CSharp-Basic-Sum

前言

基础

面试问题汇总

csharp面试基础项目汇总

https://zhuanlan.zhihu.com/p/56522099

https://www.w3cschool.cn/csharp/csharp-yzu92pj0.html

https://www.cnblogs.com/yuan-jun/p/6391192.html

  1. ref out
    -ref 必须初始化 ,里外方向都可以
    -out 必须赋值返回
    string作为特殊的引用类型,其操作是与值类型看齐的,若要将方法内对形参赋值后的结果传递出来,需要加上ref或out关键字。

  2. public protected private

  3. 委托 将方法作为参数传入另一种方法:
    在C#中,委托(delegate)是一种引用类型,在其他语言中,与委托最接近的是函数指针,但委托不仅存储对方法入口点的引用,还存储对用于调用方法的对象实例的引用。

    public void delegate MyEventHandler();
    MyEventHandler myDelegate = new MyEventHandler(Class.Method);
    myDelegate += ClassX.MethodX;
    myDelegate();
    参考:
    https://blog.csdn.net/syx920301/article/details/6702747
    https://docs.microsoft.com/zh-cn/dotnet/csharp/tour-of-csharp/delegates
    https://blog.csdn.net/Mr_Sun88/article/details/83689638
    https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/events/

  4. 索引器
    索引器允许类或结构的实例按照与数组相同的方式进行索引。

  5. 用.net做B/S结构的系统,您是用几层结构来开发,每一层之间的关系以及为什么要这样分层?

    使用MVC模式分层
    一般为3层:数据访问层,业务层,表示层。
    数据访问层对数据库进行增删查改。
    业务层一般分为二层,业务表观层实现与表示层的沟通,业务规则层实现用户密码的安全等。
    表示层为了与用户交互例如用户添加表单。

  6. string问题
    string str = null 是不给他分配内存空间,而string str = “” 给它分配长度为空字符串的内存空间。
    String s = new String(“xyz”);创建两个对象,一个是“xyx”,一个是指向“xyx”的引用对象s。

    当你给一个字符串重新赋值之后,老值并没有销毁,而是重新开辟一块空间存储新值,当程序结束后,GC扫描整个内存,如果发现有的空间没有被指向,则立即把它销毁;

  7. GC问题
    System.gc()
    Runtime.getRuntime().gc()

  8. 反射

  9. 常用类和接口
    常用的类:StreamReader、WebClient、Dictionary<K,V>、StringBuilder、SqlConnection、FileStream、File、Regex、List
    常用的接口:IDisposable、IEnumerable、IDbConnection、IComparable、ICollection、List、IDictionary

  10. 数据库里拿数据的命令

  11. .NETeventhandler】
    https://docs.microsoft.com/en-us/dotnet/api/system.eventhandler-1?view=netframework-4.7.2

字段 属性 方法

字段一般是private,尽量禁止外部访问,通过相应的属性来控制访问。

1
2
3
4
5
6
private int _age; 
public int Age
{
get{return _age;}
set{_age = value;}
}

结构体和类

结构可带有方法、字段、索引、属性、运算符方法和事件。
结构成员不能指定为 abstract、virtual 或 protected。
当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。

与类相比:
类是引用类型,结构是值类型。
结构不能继承其他的结构或类,但可实现一个或多个接口。
结构不能声明默认(无参)的构造函数。不能定义析构函数。
结构体字段无法赋初值。

面向对象

C# 面向对象

https://www.cnblogs.com/Rock-Lee/p/7912646.html

实例化

值类型:

下面说到的三种方法,好像不常用。
InstanceByNewKey.Create() 用 New 关键字实例化一个类;
InstanceByActivator.Create() 用 Activator 实例化一个类;
InstanceByAssembly.Create() 用 Assembly 实例化一个类。

值类型:

int类型的初始化,栈内存中分配int等所有值类型的变量。

到底是不是对象实例化?看上去定义里面是struct结构体类型,实际上是从system.valuetype中派生的。可以直接给它分配值。分配后在内存中的变化呢?

1.不能从值类型派生新类型,但可以结构实现接口;
2.值类型不能包含 null 值;
3.每个值类型都具有一个初始化该类型的默认值的隐式默认构造函数。

每一个值类型都有一个独立的内存区域保存自己的值,调用它的时候调用的是它的值,而引用类型调用的是内存中的地址。

引用类型:

只使用new关键字来进行对象的实例化。实例化的时候分配内存。返回对象的引用。

堆中存值,栈中存地址。string特殊,虽然是引用类型,但是复制的时候是单独创建string一份深拷贝。

下面说到的三种方法,好像不常用。
InstanceByNewKey.Create() 用 New 关键字实例化一个类;
InstanceByActivator.Create() 用 Activator 实例化一个类;
InstanceByAssembly.Create() 用 Assembly 实例化一个类。

参考阅读:

https://www.runoob.com/csharp/csharp-data-types.html

this 关键字

1 限定类似名称隐藏的成员
2 将对象作为参数传递给方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Employee
{
private string name;
private string alias;
private decimal salary = 3000.00m;
// Constructor:
public Employee(string name, string alias)
{
// Use this to qualify the fields, name and alias:
this.name = name;
this.alias = alias;
}
// Printing method:
public void printEmployee()
{
Console.WriteLine("Name: {0}\nAlias: {1}", name, alias);
// Passing the object to the CalcTax method by using this:
Console.WriteLine("Taxes: {0:C}", Tax.CalcTax(this));
}

public decimal Salary
{
get { return salary; }
}
}
class Tax
{
public static decimal CalcTax(Employee E)
{
return 0.08m * E.Salary;
}
}
class MainClass
{
static void Main()
{
// Create objects:
Employee E1 = new Employee("Mingda Pan", "mpan");
// Display results:
E1.printEmployee();
}
}
/*
Output:
Name: Mingda Pan
Alias: mpan
Taxes: $240.00
*/

3 声明索引器

1
2
3
4
5
public int this[int param]
{
get { return array[param]; }
set { array[param] = value; }
}

Base关键字

base始终指向父类

static关键字

这两个关键字不能在静态方法中使用,静态成员不是实例的一部分

外部通过关键字new来创建类的实例,但是如果类的构造函数是私有的,就没法从外部通过new创建类的实例了!

构造函数修饰符只有public或者private

Override&New

在 C# 中,派生类中的方法可具有与基类中的方法相同的名称。 可使用 new 和 override 关键字指定方法的交互方式。 override 修饰符用于扩展基类 virtual 方法,而 new 修饰符用于隐藏可访问的基类方法 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
namespace OverrideAndNew  
{
class Program
{
static void Main(string[] args)
{
BaseClass bc = new BaseClass();
DerivedClass dc = new DerivedClass();
BaseClass bcdc = new DerivedClass();
// The following two calls do what you would expect. They call
// the methods that are defined in BaseClass.
bc.Method1();
bc.Method2();
// Output:
// Base - Method1
// Base - Method2

// The following two calls do what you would expect. They call
// the methods that are defined in DerivedClass.
dc.Method1();
dc.Method2();
// Output:
// Derived - Method1
// Derived - Method2

// The following two calls produce different results, depending
// on whether override (Method1) or new (Method2) is used.
bcdc.Method1();
bcdc.Method2();
// Output:
// Derived - Method1
// Base - Method2
}
}

class BaseClass
{
public virtual void Method1()
{
Console.WriteLine("Base - Method1");
}
public virtual void Method2()
{
Console.WriteLine("Base - Method2");
}
}

class DerivedClass : BaseClass
{
public override void Method1()
{
Console.WriteLine("Derived - Method1");
}
public new void Method2()
{
Console.WriteLine("Derived - Method2");
}
}
}

参考:
csharp官方指南
在 C# 中,派生类可以包含与基类方法同名的方法。
1 基类方法必须定义为 virtual。
2 如果派生类中的方法前面没有 new 或 override 关键字,则编译器将发出警告,该方法将如同存在 new 关键字一样执行操作。
3 如果派生类中的方法前面带有 new 关键字,则该方法被定义为独立于基类中的方法。
4 如果派生类中的方法前面带有 override 关键字,则派生类的对象将调用该方法,而不是调用基类方法。
5 可以从派生类中使用 base 关键字调用基类方法。
6 override、virtual 和 new 关键字还可以用于属性、索引器和事件中。

封装

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class A
{
public void getvalue()
{
Debug.Log ("class A getvalue method called...");
}
}

public class B:A
{
public void getsomething()
{
this.getvalue ();//如果改成base,则肯定调用父类的方法
}
public new void getvalue()//加不加new都一样
{
Debug.Log ("class B getvalue method called...");
}
}

//output B method called
B b = new B ();
b.getsomething ();

继承、父类初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Point
{
public int x, y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}
public class Point3D: Point
{
public int z;
public Point3D(int x, int y, int z) : base(x, y)
{
this.z = z;
}
}

Interface

默认public 不用加任何关键字。
接口必须实现。实现的时候直接实现就行。
一般是隐式实现,类和接口声明都可以调用到。

概念:极度抽象的类,无成员变量,无实例属性和实例方法,只有抽象方法或抽象属性,生活中的例子:标准,规则。
写法:接口不用class,用interface,名字一般以I作为首字母;不用写abstract,里面所有都是,不用写public,必须是public。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface ISampleInterface
{
void SampleMethod();
}

class ImplementationClass : ISampleInterface
{
//interface member implementation:
void SampleMethod()
{
// Method implementation.
}

static void Main()
{
// Declare an interface instance.
ISampleInterface obj = new ImplementationClass();

// Call the member.
obj.SampleMethod();
}
}
Abstract

关键字abstract

抽象方法:(可以看成一个虚函数)
public abstract void AbstractMethod1();//没有方法实体

抽象类:
有抽象方法的类必须是抽象类,只能被继承,不能被实例化。反之,抽象类也可以有非抽象方法。
抽象类继承(抽象方法重写)才有意义。
如果抽象方法要在子类中实现,必须用override。如果抽象类中的方法在子类中没有被全部实现,那么这个子类还是抽象类。
抽象方法也是virtual的,但是不需要加关键字。

参考:
http://www.cnblogs.com/flyinthesky/archive/2008/06/18/1224774.html
https://blog.csdn.net/yl2isoft/article/details/16850971

Virtual

Virtual CallSomeOne();

声明该方法,表示可以被override重写,但非强制。重写时肯定用override。
基类中定义了virtual,子类中不重写,那么会调用基类方法。
有啥用?
virtual和new的区别:
virtual扩展了基类的方法。
new则隐藏了基类中的方法。表示你的这个方法跟基类的同名方法是独立的。

Static

静态方法:
当一个方法被声明为Static时,这个方法是一个静态方法,编译器会在编译时保留这个方法的实现。也就是说,这个方法属于类,但是不属于任何成员,不管这个类的实例是否存在,它们都会存在。就像入口函数Static void Main,因为它是静态函数,所以可以直接被调用。
1.存储在静态成员中的信息只有在应用程序退出的时候才会从内存里释放出来。
2.静态成员不能通过对象访问,只能通过类名直接访问。
3.静态成员(内容)在内存中只存储一份。???存在静态区???
4.静态成员,在整个应用程序中任何一个地方都可以访问的到,所以静态成员直到程序退出后才会释放内存。而实例成员, 当没有变量使用就可以被垃圾回收,回收后内存就释放了.(可以把一些常用的工具函数,封装到一个静态类中,使用方便)
静态类:
1.在静态类中只能有静态成员,不能有实例成员
2.静态类不能创建对象,不能new对象
使用静态类跟使用静态成员的情景其实是一样的,即在整个应用程序中要共享数据的时候可以使用静态类。所以对于那些类中包含有大量的方法,并且类不需要创建对象的时候,可以使用静态类。

参考:
https://blog.csdn.net/u012252959/article/details/48262775

多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class Fruit
{
public Fruit()
{
Debug.Log("1st Fruit Constructor Called");
}
public void Chop()
{
Debug.Log("The fruit has been chopped.");
}
public void SayHello()
{
Debug.Log("Hello, I am a fruit.");
}
}
public class Apple : Fruit
{
public Apple()
{
Debug.Log("1st Apple Constructor Called");
}
//In this example, the "new" keyword is used to supress
//warnings from Unity while not overriding the methods
//in the Apple class.
public new void Chop()
{
Debug.Log("The apple has been chopped.");
}
public new void SayHello()
{
Debug.Log("Hello, I am an apple.");
}
}
public class FruitSalad : MonoBehaviour
{
void Start ()
{
//Notice here how the variable "myFruit" is of type
//Fruit but is being assigned a reference to an Apple. This
//works because of Polymorphism. Since an Apple is a Fruit,
//this works just fine. While the Apple reference is stored
//in a Fruit variable, it can only be used like a Fruit
Fruit myFruit = new Apple();

myFruit.SayHello();
myFruit.Chop();
//This is called downcasting. The variable "myFruit" which is
//of type Fruit, actually contains a reference to an Apple. Therefore,
//it can safely be turned back into an Apple variable. This allows
//it to be used like an Apple, where before it could only be used
//like a Fruit.
Apple myApple = (Apple)myFruit;
myApple.SayHello();
myApple.Chop();
}
}

常用数据类型的使用

int string转换

(int) //int.Parse //int.TryParse//Convert.ToInt32
在进行数据转换前选择转换方法要谨慎,
如果是数字类型(float,int,uint)可以考虑直接用(int)强制转换,
如果是整型字符串类型的,考虑用int.Parse()进行转换,即各种整型ToString()之后的形式,不能为浮点型
如果不是这两种类型,再考虑用Convert.ToInt32()进行转换。

  1. int转成string
    用toString
    或者Convert.toString()如下

    例如:
    int varInt = 1;
    string varString = Convert.ToString(varInt);
    string varString2 = varInt.ToString();

  2. string转成int
    如果确定字符串中是可以转成数字的字符,可以用int.Parse(string s),该语句返回的是转换得到的int值;
    如果不能确定字符串是否可以转成数字,可以用int.TryParse(string s, out int result),该语句返回的是bool值,指示转换操作是否成功,参数result是存放转换结果的变量。

    例如:
    string str = string.Empty;
    str = “123”;
    int result=int.Parse(str);

string str = string.Empty;
str = “xyz”;
int result;
int.TryParse(str, out result);

常用
https://blog.csdn.net/OnafioO/article/details/44525651

数组Array:

1
2
3
4
5
6
string[] names = new string[2];
int[] numbers = new int[5] { 4, 3, 8, 0, 5 };
//better:
int[] numbers = { 4, 3, 8, 0, 5 };
Array.Sort(numbers);
foreach (int i in numbers);

ArrayList动态数组

ArrayList test = new ArrayList();

类型不安全

1
2
3
4
5
6
7
ArrayList list = new ArrayList();
list.Add("John Doe");
list.Add("Jane Doe");
list.Add("Someone Else");

foreach(string name in list)
Console.WriteLine(name);

List

List test = new List();
.add/.removeat

1
2
3
4
5
6
7
8
9
10
11
List<Car> myList= new List<Car>() { 
new Car {Make = “asdas”,Model = “haha”},
new Car {Make = “asdsd”,Model = “asdasd"}
};

List<string> listOfStrings = new List<string>();
listOfStrings.Add("a string");
List<string> listOfNames = new List<string>(){ "John Doe","Jane Doe","Joe Doe"};
listOfNames.Insert(0, "John Doe");

foreach(string str in listofstrings);

Dictionary 类型安全

Dictionary 使用前确保key是存在的,然后获取value。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Dictionary<string, string> openWith = new Dictionary<string, string>();

openWith.Add("txt", "notepad.exe");
openWith["rtf"] = "winword.exe";

foreach (string key in openWith.Keys)...
foreach (string value in openWith.Values)...

Dictionary<string, string>.ValueCollection valueColl = openWith.Values;
foreach (string s in valueColl)

foreach (KeyValuePair<string, string> kvp in openWith)
{
​ Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value);
}

openWith.Remove("doc");

if (openWith.ContainsKey("bmp"))

Dictionary ,接口 identity,dictionary
简单用法:

1
2
3
4
5
6
7
8
9
10
/* 
* Dictionary:Dictionary:字典:属于泛型类容器,用来存储一对键值对
* 1.命名空间:System.Collections.Generic
* 2.构造方法:Dictionary <TKey,TValue> dic=new Dictionary<TKey,TValue>();
* 3.常用属性:Count:返回字典中键值对的数目
* 4.常用方法:
* Add(TKey,TValue):要添加的键值对
* TryGetValue(TKey):获得与指定键相关联的值
* Remove(TKey):从字典中移除
*/

HashTable

类型不安全。线程安全,单线程写入,多线程读取。

https://blog.csdn.net/snlei/article/details/3939206

https://www.cnblogs.com/cxchen/p/7278652.html

进阶

文件IO

FileStream
StreamWriter/StreamReader
unity网络用的NetworkStream

索引器

C#通过提供索引器,可以象处理数组一样处理对象。特别是属性,每一个元素都以一个get或set方法暴露。索引器不单能索引数字(数组下标),还能索引一些HASHMAP的字符串,所以,通常来说,C#中类的索引器通常只有一个,就是THIS,但也可以有无数个,只要你的参数列表不同就可以了。索引器和返回值无关, 索引器最大的好处是使代码看上去更自然,更符合实际的思考模式.

索引器允许类或结构的实例按照与数组相同的方式进行索引。
索引器类似于属性,不同之处在于它们的访问器采用参数。

在下面的示例中,定义了一个泛型类(class SampleCollection),并为其提供了简单的 get 和 set 访问器方法(作为分配和检索值的方法)。Program 类为存储字符串创建了此类的一个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class SampleCollection<T>
{
private T[] arr = new T[100];
public T this[int i] //注意,定义索引器。this 关键字用于定义索引器。
{
get
{
return arr[i]; //访问器采用参数
}
set
{
arr[i] = value; //访问器采用参数
}
}
}
// This class shows how client code uses the indexer
class Program
{
static void Main(string[] args)
{
SampleCollection<string> stringCollection = new SampleCollection<string>();
stringCollection[0] = "Hello, World"; //这里 使用索引器进行引用
System.Console.WriteLine(stringCollection[0]);
}
}

索引器使得对象可按照与数组相似的方法进行索引。
get 访问器返回值。set 访问器分配值。
this 关键字用于定义索引器。
value 关键字用于定义由 set 索引器分配的值。
索引器不必根据整数值进行索引,由您决定如何定义特定的查找机制。
索引器可被重载。
索引器可以有多个形参,例如当访问二维数组时。

LINQ

myCar是一个List

方式一 LINQ query

1
2
3
var bmws = from car in myCars 
where car.Make = “aaa”
select car;

方式二 LINQ method

1
2
var bmws = myCars.Where(p=>p.Make == “aaa”); 
foreach(var car in bmws)...

单例

单例的设计一般用于通信的或者一些模块之间的耦合,只能有一个实例化的对象。
核心是两个点:私有的对象成员;公有的静态访问方法(如果没有对象,实例化一个再返回);

一般的csharp类

1
2
3
4
5
6
7
8
9
10
11
12
public class singleton
{
private static singleton _instance = null;
public static singleton GetInstance()
{
if (_instance == null)
{
_instance = new singleton ();
}
return _instance;
}
}

unity常用的monoehaviour单例类

一 属性方法 需要挂脚本/手动实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class singleton:monobehaviour
{
static singleton _instance;
void awake(){
_instance = this;
}
public static singleton GetInstance()*//公有静态属性获取*
{
get
{
return _instance;
}
}
}

二 自动创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class singleton:MonoBehaviour
{
private static singleton _instance = null;
void Awake()
{
_instance = this;
}
public static singleton GetInstance()
{
if (_instance == null)
{
GameObject go = new GameObject("_MyClass"); *//* *创建一个新的GameObject*
DontDestroyOnLoad(go); *//* *防止被销毁*
_instance = go.AddComponent<singleton>(); *//* *将实例挂载到GameObject上*
}
return _instance;
}
}

线程安全单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ThreadSafeSingleton
{
private static volatile ThreadSafeSingleton _instance;
private static object _lock = new object();
public static ThreadSafeSingleton GetSingleton()
{
if (_instance == null)
{
lock(_lock)
{
if (_instance == null)
_instance = new ThreadSafeSingleton ();
}
}
return _instance;
}
}

多线程

在unity开发中使用多线程的总结:
https://www.jb51.net/article/82800.htm
一个特别好的c#线程简单例子:
https://www.cnblogs.com/dotnet261010/p/6159984.html
http://www.runoob.com/csharp/csharp-multithreading.html

多线程文章系列
http://www.cnblogs.com/zpx1986/p/5571506.html

比较Thread、Task、await等关键字的方法:

https://www.cnblogs.com/doforfuture/p/6293926.html

Thread:

每次都会实例化一个新线程

Task :

https://www.cnblogs.com/wjcnet/p/6955756.html

与ThreadPool类似,从线程池中调用。

开启新任务的方法:Task.Run()或者Task.Factory.StartNew(),开启的是后台线程

要在主线程中等待后台线程执行完毕,可以使用Wait方法(会以同步的方式来执行)。不用Wait则会以异步的方式来执行。

1
2
3
4
5
6
7
8
Console.WriteLine("主线程启动");
Task task = Task.Run(() => {
Thread.Sleep(1500);
Console.WriteLine("task启动");
});
Thread.Sleep(300);
task.Wait();
Console.WriteLine("主线程结束");

线程同步问题
-sleep
-lock
-信号和句柄 AutoResetEvent/ManualResetEvent,调用Set()和WaitOne())?????????????????

Lock

https://www.cnblogs.com/apsnet/archive/2012/07/08/2581475.html
https://www.cnblogs.com/doforfuture/p/6293926.html

控制多线程对同一个对象的同时读写。
当同步对共享资源的线程访问时,请锁定专用对象实例(例如,private readonly object balanceLock = new object();)或另一个不太可能被代码无关部分用作 lock 对象的实例。 避免对不同的共享资源使用相同的 lock 对象实例,因为这可能导致死锁或锁争用。 具体而言,避免将以下对象用作 lock 对象:
1 this(调用方可能将其用作 lock)。
2 Type 实例(可以通过 typeof 运算符或反射获取)。
3 字符串实例,包括字符串文本,(这些可能是暂存的)。
为什么不要用lock this:
https://www.cnblogs.com/wolf-sun/p/8405541.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using System;
using System.Threading.Tasks;

public class Account
{
private readonly object balanceLock = new object();
private decimal balance;

public Account(decimal initialBalance)
{
balance = initialBalance;
}

public decimal Debit(decimal amount)
{
lock (balanceLock)
{
if (balance >= amount)
{
Console.WriteLine($"Balance before debit :{balance, 5}");
Console.WriteLine($"Amount to remove :{amount, 5}");
balance = balance - amount;
Console.WriteLine($"Balance after debit :{balance, 5}");
return amount;
}
else
{
return 0;
}
}
}

public void Credit(decimal amount)
{
lock (balanceLock)
{
Console.WriteLine($"Balance before credit:{balance, 5}");
Console.WriteLine($"Amount to add :{amount, 5}");
balance = balance + amount;
Console.WriteLine($"Balance after credit :{balance, 5}");
}
}
}

class AccountTest
{
static void Main()
{
var account = new Account(1000);
var tasks = new Task[100];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = Task.Run(() => RandomlyUpdate(account));
}
Task.WaitAll(tasks);
}

static void RandomlyUpdate(Account account)
{
var rnd = new Random();
for (int i = 0; i < 10; i++)
{
var amount = rnd.Next(1, 100);
bool doCredit = rnd.NextDouble() < 0.5;
if (doCredit)
{
account.Credit(amount);
}
else
{
account.Debit(amount);
}
}
}
}

Loom

unity是帧序列调用的,严格来讲并不是特别支持真正的多线程。一般的几种情况如下:

  1. 变量都是共享的
  2. Unity Engine定义的基本结构int,float,struct如Vector3可以在分线程中计算,其他Texture2d就不行
  3. 非UnityEngine API 都可以在分线程运行

Loom的源代码就100多行,写的很精髓,可以自行研究下。最常用的是你在分线程里没法调用主线程的一些控件,但是通过Loom就可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class testLoom : MonoBehaviour
{
public Text mText;
void Start ()
{
// 用Loom的方法调用一个线程
Loom.RunAsync(
() =>
{
Thread thread = new Thread(RefreshText);
thread.Start();
});
}

private void RefreshText()
{
// 用Loom的方法在Unity主线程中调用Text组件
Loom.QueueOnMainThread((param) =>
{
mText.text = "Hello Loom!";
},null);
}
}

socket汇总

https://www.cnblogs.com/JinT-Hwang/p/10114027.html

https://www.cnblogs.com/AbstractLee/p/8623454.html

多线程异步socket
参考:详细的unity socket 异步多线程总结
https://blog.csdn.net/linshuhe1/article/details/76580764
https://blog.csdn.net/naruto2011sasuke/article/details/77046878

https://blog.csdn.net/claine/article/details/52374546

范型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class SomeOtherClass : MonoBehaviour
{
void Start ()
{
SomeClass myClass = new SomeClass();
//In order to use this method you must
//tell the method what type to replace
//'T' with.
myClass.GenericMethod<int>(5);
}
}
//Here is a generic class. Notice the generic type 'T'.
//'T' will be replaced with an actual type, as will also
//instances of the type 'T' used in the class.
public class GenericClass <T>
{
T item;
public void UpdateItem(T newItem)
{
item = newItem;
}
}
public class GenericClassExample : MonoBehaviour
{
void Start ()
{
//In order to create an object of a generic class, you must
//specify the type you want the class to have.
GenericClass<int> myClass = new GenericClass<int>();
myClass.UpdateItem(5);
}
}

委托和事件

一般实现时间的注册、监听等用delegate类实现起来比较自由,灵活性也高。
参考本人日志EventHandler。

完全的参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/delegates/

1 举一个简单的例子:

两个方法,MAX和MIN,都是 int MAX(int a,int b)类型。
定义我的委托:
delegate int myDelegate(int a,int b);
myDelegate md = null;
两个checkbox,分别实现
this.md = new myDelegate(this.MAX);
this.md = new myDelegate(this.MIN);
用户点击计算按钮的时候:
if(this.md == null) return;
int c = this.md(inout.a,inout.b);
this.text = c.tostring();

2 多播:

声明委托

public void delegate MYMulticastDelegate(string path);

//创建委托,并指向CreateLogFile方法
MyMulticastDelegate logDelegate = new MyMulticastDelegate(CreateLogFile);
//创建委托,并指向WriteToDb
MyMulticastDelegate dbDelagate = new MyMulticastDelegate(WriteToDb);
MyMulticastDelegate multicastDelegate = logDelegate;
//在多播委托的调用链中添加新的委托元素
multicastDelegate = multicastDelegate + dbDelagate;
//调用多播委托,并且序列执行两个委托所指的方法
multicastDelegate(“new file.txt”);

自己常用的是Action和Func类,用来声明触发和调用都比较实用简单。参考Unity测试脚本。

3 一个非常简单的委托完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 小张类
public class MrZhang
{
public static void BuyTicket()
{
Console.WriteLine("NND,每次都让我去买票,鸡人呀!");
}
public static void BuyMovieTicket()
{
Console.WriteLine("我去,自己泡妞,还要让我带电影票!");
}
}
//小明类
class MrMing
{
// 声明一个委托,其实就是个“命令”
public delegate void BugTicketEventHandler();
public static void Main(string[] args)
{
// 这里就是具体阐述这个命令是干什么的,本例是MrZhang.BuyTicket“小张买车票”
BugTicketEventHandler myDelegate = new BugTicketEventHandler(MrZhang.BuyTicket);
myDelegate += MrZhang.BuyMovieTicket;// 这时候委托被附上了具体的方法
myDelegate();
}
}

4 csharp也支持匿名委托,结合Lamda表达式,多线程编程代码会很方便。
https://blog.csdn.net/syx920301/article/details/6702747

内存相关

内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A
{
int a = 12
}

class B
{
static void Main(string[] args)
{
A C1 = new A();
A C2 = new A();
Console.WriteLine("C1.a");
C2.a=6
Console.WriteLine("C2.a");
}
}

结果输出的是 12 6 吗?我的问题是当A被实例化两次,a变量是在内存中如何分配的??都分配在堆栈中吗??
首先,堆和栈是两码事。堆(heap)主要用于动态内存分配,而栈(stack)主要是用于函数内自动变量的空间分配。
在C#中,任何引用类型的变量,都是在堆上分配的。(强制声明stackalloc等特殊情况除外)
各种class就是引用类型,所以都是在堆上分配的。
你那个例子,有两个变量,一个是C1,一个是C2,它们各自有一个成员a,彼此互不影响。
二者都分配在堆上,位置不同。所以你C1中a的值是12,C2中a的值是6,二者之间没有影响。
“值类型变量存储在栈中” 这句话不完全正确。只有在函数内部直接定义的值类型,比如你在函数内部定义了一个int a,它才是在stack上面分配的。而对象内部的成员变量(除非静态),不管是值还是引用类型,它都只是整个对象的一部分,而整个对象是在堆上分配的。

类似地参考:
https://bbs.csdn.net/topics/391995748?page=1
https://bbs.csdn.net/topics/300084871

在c#中,当我们将一个class实例化后,系统会给class里面的方法分配内存吗?如果分配的话是每个实例化的方法都分配还是实例化对象共享一块方法分配的内存?

方法的实例只有一份,所有实例共享。
类是引用类型的,实例化时会在托管堆中创建一块内存,存放类中的变量值,及类本身的一些标识信息,而方法最终于也是操作变量,所以不会给方法分配内存
另外,类每实例化一次时都会重新创建新的内存空间,当类实例不在被任何对象引用时,GC 就会遍历到这个对象,然后把它标记为删除,在某一时刻清除实例所占用的内存。

参考:
内存管理:https://www.cnblogs.com/flyptt/archive/2013/12/16/3477512.html
C语言内存,特别详细的分析 https://www.cnblogs.com/tuhooo/p/7221136.html
C#内存管理和回收超经典文章
http://www.cnblogs.com/riccc/archive/2009/09/01/dotnet-memory-management-and-garbage-collection.html
https://zhidao.baidu.com/question/339055693.html

托管和非托管资源

####托管资源

[图片托管]

托管

.NET中的所有类型都是(直接或间接)从System.Object类型派生的。CTS中的类型被分成两大类——引用类型(reference type,又叫托管类型[managed type]),分配在内存堆上;值类型(value type),分配在堆栈上。

[图片参考托管]

​ 托管类型 非托管类型

非托管资源

ApplicationContext, Brush, Component, ComponentDesigner, Container, Context, Cursor, FileStream, Font, Icon, Image, Matrix, Object, OdbcDataReader, OleDBDataReader, Pen, Regex, Socket, StreamWriter, Timer, Tooltip, 文件句柄, GDI资源, 数据库连接等等资源。

GC的问题

首先,GC并不是能释放所有的资源。它不能自动释放非托管资源。
第二,GC并不是实时性的,这将会造成系统性能上的瓶颈和不确定性。
GC并不是实时性的,这会造成系统性能上的瓶颈和不确定性。所以有了IDisposable接口,IDisposable接口定义了Dispose方法,这个方法用来供程序员显式调用以释放非托管资源。使用using语句可以简化资源管理。

判断一个对象为垃圾的办法

计数器算法

每当一个对象被引用,计数器+1,引用失效,计数器减1.计数器为0的对象就会被当作垃圾回收。
效率高,但是循环引用没法甄别。

可达性算法

Garbage Collector(垃圾收集器,在不至于混淆的情况下也成为GC)以应用程序的root为基础,遍历应用程序在Heap上动态分配的所有对象[2],通过识别它们是否被引用来确定哪些对象是已经死亡的、哪些仍需要被使用。已经不再被应用程序的root或者别的对象所引用的对象就是已经死亡的对象,即所谓的垃圾,需要被回收。这就是GC工作的原理。为了实现这个原理,GC有多种算法。比较常见的算法有Reference Counting,Mark Sweep,Copy Collection等等。目前主流的虚拟系统.NET CLR,Java VM和Rotor都是采用的Mark Sweep算法。

通过一系列的称为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为 引用链(Reference Chain) ,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

对象销毁和垃圾回收延伸阅读:
https://www.cnblogs.com/yang_sy/p/3784151.html

装箱和拆箱

本质上,所有的类型基类都是object,引用类型;
装箱,值-引用:

1
2
3
int val = 100; 
object obj = val;
Console.WriteLine ("对象的值 = {0}", obj); //对象的值 = 100

拆箱,引用-值:

1
2
3
4
int val = 100; 
object obj = val;
int num = (int) obj;
Console.WriteLine ("num: {0}", num); //num: 100

装箱以后,才能被拆箱。

参考:
https://blog.csdn.net/qiaoquan3/article/details/51439726
http://www.cnblogs.com/huashanlin/archive/2007/05/16/749359.html

序列化和反序列化

值类型引用类型、堆栈

C#中定义的值类型包括原类型
(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举(enum)、结构(struct),

引用类型包括
class、数组、接口、委托、string等。

值类型-栈
引用类型-堆

延伸阅读:
https://blog.csdn.net/just0kk/article/details/51523374
http://www.cnblogs.com/siqing99/archive/2012/04/03/2430918.html
https://www.cnblogs.com/bakuhert/articles/5878086.html
https://blog.csdn.net/qiaoquan3/article/details/51202926
https://blog.csdn.net/qq_26545305/article/details/52903658

深拷贝和浅拷贝

为对象创建副本的技术称为拷贝(也叫克隆)。我们将拷贝分为浅拷贝和深拷贝。图表总结:

浅拷贝: 值类型 真拷贝/引用类型 假拷贝
深拷贝:值类型 真拷贝/引用类型 真拷贝

特殊:浅拷贝中,string看成值类型
无论是浅拷贝还是深拷贝,微软都建议用类型继承ICloneable接口的方式明确告诉调用者:该类型可以被拷贝。当然,ICloneable接口只提供了一个声明为Clone的方法,我们可以根据需求在Clone方法内实现浅拷贝或深拷贝。
浅拷贝中,只是实现了引用的拷贝,只有一层拷贝。
值类型被拷贝了出来,有独立的一份;
引用类型只拷贝了引用,修改新对象,会全部修改原对象;

[但是,string类型比较特殊,虽然是引用类型,但是仍然会被创建副本。]

浅拷贝 将对象中的所有字段复制到新的对象(副本)中。其中,值类型字段的值被复制到副本中后,在副本中的修改不会影响到源对象对应的值。而引用类型的字段被复制到副本中的是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对象本身。

深拷贝 同样,将对象中的所有字段复制到新的对象中。不过,无论是对象的值类型字段,还是引用类型字段,都会被重新创建并赋值,对于副本的修改,不会影响到源对象本身。

实现浅拷贝
使用MemberwiseClone,Room为引用类型,Film为值类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Room
{
public int _maxSeat;
public Room(int maxSeat)
{
this._maxSeat = maxSeat;
}
}

public struct Film
{
public string _name;
public Film(string name)
{
this._name = name;
}
}

public class Cinema
{
public Room _room;
public Film _film;
public Cinema(Room room, Film film)
{
this._room = room;
this._film = film;
}

public object Clone()
{
return MemberwiseClone(); //对引用类型实施浅复制
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void Main(string[] args)
{
Room room1 = new Room(60);
Film film1 = new Film("家园防线");
Cinema cinema1 = new Cinema(room1, film1);
Cinema cinema2 = (Cinema)cinema1.Clone();
Console.WriteLine("拷贝之前,结构成员的字段值为{0},引用类型成员的字段值为{1}", cinema1._film._name,cinema1._room._maxSeat);

Console.WriteLine("拷贝之后,新的结构成员的字段值为{0},引用类型成员的字段值为{1}", cinema2._film._name, cinema2._room._maxSeat);

//修改拷贝之前引用类型的字段值
cinema1._film._name = "极品飞车";
cinema1._room._maxSeat = 80;

Console.WriteLine("修改之后,结构成员的字段值为{0},引用类型成员的字段值为{1}", cinema1._film._name, cinema1._room._maxSeat);
Console.WriteLine("修改之后,新的结构成员的字段值为{0},引用类型成员的字段值为{1}", cinema2._film._name, cinema2._room._maxSeat);

Console.ReadKey();
}

结果:

拷贝前:结构 家园,引用 60;

拷贝后:新结构 家园,引用 60;

修改前:结构 极品,引用 80;

修改后:新结构 家园,引用 80;

实现深拷贝

4种方法

1 二进制的序列化和反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static T DeepCopy<T>(T obj)
{
object retval;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
//序列化成流
bf.Serialize(ms, obj);
ms.Seek(0, SeekOrigin.Begin);
//反序列化成对象
retval = bf.Deserialize(ms);
ms.Close();
}
return (T)retval;
}

2 XML序列化和反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
public T DeepCopy<T>(T obj)
{
object retval;
using (MemoryStream ms = new MemoryStream())
{
XmlSerializer xml = new XmlSerializer(typeof(T));
xml.Serialize(ms, obj);
ms.Seek(0, SeekOrigin.Begin);
retval = xml.Deserialize(ms);
ms.Close();
}
return (T)retval;
}

3 反射实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static T DeepCopy<T>(T obj)
{
//如果是字符串或值类型则直接返回
if (obj is string || obj.GetType().IsValueType) return obj;

object retval = Activator.CreateInstance(obj.GetType());
FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
foreach (FieldInfo field in fields)
{
try { field.SetValue(retval, DeepCopy(field.GetValue(obj))); }
catch { }
}
return (T)retval;
}

4 利用silverlight DataContractSerializer实现,用于在silverlight 客户端使用

1
2
3
4
5
6
7
8
9
10
11
12
public static T DeepCopy<T>(T obj)
{
object retval;
using (MemoryStream ms = new MemoryStream())
{
DataContractSerializer ser = new DataContractSerializer(typeof(T));
ser.WriteObject(ms, obj);
ms.Seek(0, SeekOrigin.Begin);
retval = ser.ReadObject(ms);
ms.Close();
}
return (T)retval;

补充:第一个已经通过递归实现了深拷贝。

同浅拷贝类似的实现代码:(注意 需要序列化的类必须标明)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
[Serializable]
public class Room{
public int _maxSeat;

public Room()
{}
public Room(int maxSeat)
{
this._maxSeat = maxSeat;
}
}

[Serializable]
public struct Film{
public string _name;
public Film(string name)
{
this._name = name;
}
}

[Serializable]
public class Cinema
{
public Room _room;
public Film _film;
public Cinema(Room room, Film film)
{
this._room = room;
this._film = film;
}

//浅拷贝
//public object Clone()
//{
// return MemberwiseClone(); //对引用类型实施浅复制
//}

//深拷贝 对每个对象成员进行复制
public object Clone()
{
Room room = new Room();
room._maxSeat = this._room._maxSeat;//复制当前引用类型成员的值到新对象
Film film = this._film; //值类型直接赋值
Cinema cinema = new Cinema(room, film);
return cinema;
}

//使用序列化和反序列化进行复制
public object Clone1()
{
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, this); //复制到流中
ms.Position = 0;
return (bf.Deserialize(ms));
}
}

测试代码;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void Main(string[] args)
{
Room room1 = new Room(60);
Film film1 = new Film("家园防线");
Cinema cinema1 = new Cinema(room1, film1);
Cinema cinema2 = (Cinema)cinema1.Clone1();
Console.WriteLine("拷贝之前,结构成员的字段值为{0},引用类型成员的字段值为{1}", cinema1._film._name,cinema1._room._maxSeat);

Console.WriteLine("拷贝之后,新的结构成员的字段值为{0},引用类型成员的字段值为{1}", cinema2._film._name, cinema2._room._maxSeat);

//修改拷贝之前引用类型的字段值
cinema1._film._name = "极品飞车";
cinema1._room._maxSeat = 80;

Console.WriteLine("修改之后,结构成员的字段值为{0},引用类型成员的字段值为{1}", cinema1._film._name, cinema1._room._maxSeat);
Console.WriteLine("修改之后,新的结构成员的字段值为{0},引用类型成员的字段值为{1}", cinema2._film._name, cinema2._room._maxSeat);

Console.ReadKey();
}

参考:

https://blog.csdn.net/mochounv/article/details/52575700

https://blog.csdn.net/bigpudding24/article/details/48490145 具体例子 很清晰

反射

19 sharp 反射机制 例子 用法等四个相关文档,最后整理成自己的思路:(三篇文章+一篇理解)http://www.cnblogs.com/wangshenhe/p/3256657.htmlhttps://blog.csdn.net/wpcxyking/article/details/6201636https://www.jianshu.com/p/e9d6c2040717
https://q.cnblogs.com/q/31052/

// https://unity3d.com/cn/learn/tutorials/topics/performance-optimization/optimizing-garbage-collection-unity-games //

语言横向比较

CPP

内存管理、函数指针(委托)、多重继承

  1. csharp不需要特别注意内存管理
  2. csharp强调类、市里的使用,而不需要和cpp一样频繁地使用指针
  3. csharp的委托相当于cpp中的函数指针

参考:

https://www.cnblogs.com/to-creat/p/4925729.html