C#高级编程_注释笔记

Type类型

1
2
3
4
var i = 2.0;
var t1 = i.GetType();
var t2 = i.GetTypeCode();
Console.WriteLine("类型:" + t1 + ",类型码:" + t2 + ",类型码的值:" + (int)t2);

输出:类型:System.Double,类型码(enum):Double,类型码的值:14

1
2
3
4
5
6
7
8
9
10
11
12
var t = typeof(Person);
Console.WriteLine(t.Assembly);
Console.WriteLine(t.Attributes);
Console.WriteLine(t.BaseType);
Console.WriteLine(t.FullName);
Console.WriteLine(t.GetProperties()[0].Name);
Console.WriteLine(t.GUID);
Console.WriteLine(t.Name);
Console.WriteLine(t.Namespace);
Person p1 = new Person();
if (p1.GetType()==t)
Console.WriteLine("Ok");

Type的成员方法

1
2
3
4
5
6
GetConstructor[s]()     //返回ConstructorInfo
GetEvent[s]() //返回EventInfo
GetField[s]() //返回FieldInfo
Get[Default]Member[s]() //返回MemberInfo
GetMethod[s]() //返回MethodInfo
GetProperty[s]() //返回PropertyInfo

Assembly和Type

1
2
3
4
Assembly ass = Assembly.Load("ConsoleApplication1");
Type t = typeof(Book);
var b = ass.CreateInstance(t.FullName);
var b1 = ass.CreateInstance("ConsoleApplication1.Book");

程序集

dll,逻辑单元。包含模块,资源等。

程序集强名:共享程序集使用强名唯一地标识该程序集,保证全局唯一,包括:

  1. 程序集本身的名称
  2. 版本号。不同版本可以共存于一个进程。
  3. 公钥。保证独一无二。
  4. 文化

应用程序域

图解

应用程序域

不同域加载程序集

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
namespace AssemblyA
{
// [Serializable]//只序列化,则仍由调用应用域访问
public class Demo : MarshalByRefObject //继承这个基类(已序列化),才能通过另一个应用程序域来访问。
{
public Demo() { }
public Demo(int val1, int val2)
{
Console.WriteLine("domain:{0}中带有参数{1}和{2}的构造函数被调用", AppDomain.CurrentDomain.FriendlyName, val1, val2);
}
public void DoSome()
{
Console.WriteLine("domain:{0}调用方法DoSome", AppDomain.CurrentDomain.FriendlyName);
}
}

class Program
{
static void Main(string[] args)
{
Console.WriteLine("domain:{0}的主函数被调用", AppDomain.CurrentDomain.FriendlyName);
}
}
}

namespace AssemblyB
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("当前域:" + AppDomain.CurrentDomain.FriendlyName);
AppDomain firstDomain = AppDomain.CreateDomain("First AppDomain");
firstDomain.ExecuteAssemblyByName("AssemblyA"); //需要添加引用或复制程序集,执行exe形式的程序集
firstDomain.ExecuteAssembly("AssemblyA.exe"); //上方主函数被调用

AppDomain secondDomain = AppDomain.CreateDomain("Second AppDomain");
var ret = secondDomain.CreateInstance("AssemblyA", "AssemblyA.Demo", true, BindingFlags.CreateInstance,
null, new object[] { 7, 3 }, null, null); //上方Demo类构造函数被调用
var obj = (Demo)ret.Unwrap(); //解除包装
obj.DoSome();

//实例化程序集中的类的示例
Demo demo = (Demo)secondDomain.CreateInstanceAndUnwrap("AssemblyA", "AssemblyA.Demo");
demo.DoSome(); //显示:Second AppDomain调用方法DoSome

//适用:如果程序集是动态加载的,用完后卸载。
//主应用程序域中,无法删除已加载程序集。但可以终止应用程序域,该域中加载的程序集会从内存中清楚。
AppDomain.Unload(secondDomain);

//string fullName = namespaceName + "." + Controller + "Controller";
//object obj = Assembly.Load(assemblyName).CreateInstance(fullName);
//result = (ServerController)obj;

Console.ReadKey();
}
}
}

当前域加载程序集

1
2
3
4
5
6
//动态加载程序集,创建实例
//配合接口降低耦合,也可以逆向引用调用对象方法
string fullName = "AssemblyA.Demo";
object obj = Assembly.Load("AssemblyA").CreateInstance(fullName);
var result = (Demo)obj;
result.DoSome();

方法和函数

函数>方法。
函数包括:方法,非数据成员:索引器,构造函数,析构函数,属性。

构造函数初始化器

1
2
3
4
5
6
7
8
9
public Car(string des) : this(des , 4)
{
//...
}

public Car(string des,int wheel)
{
//...
}

实现构造函数之间的重用: this 或: base 之后的构造函数先执行,然后再执行此构造函数。

partial关键词

编译时,两个不分类的属性,XML注释,接口,泛型类型的参数属性,特性和成员会合并。

泛型中的default关键字

初始化泛型参数T时,可以使用detault(T)。在不知道T是引用类型还是值类型的情况下,分别赋予引用类型null或者值类型0

委托

类似于C++中的函数指针(但类型不安全,参数和返回值未知)。

面向对象编程,没有方法是孤立的。如果要传递方法, 就要把方法细节(签名和返回类型)封装在一种新类型对象中,即委托。委托是一种特殊对象,普通对象都包含数据,而委托包含的只是一个或多个方法的地址(指针)。

委托的定义和使用:(面向对象的角度)

  1. 定义一个委托(类似定义一个类)
    private delegate string GetAString();

  2. 实例化一个GetAString的委托实例(实例化一个对象)。委托语法上总是接收一个参数的构造函数。
    GetAString me=new GetAString(x.ToString);
    或者委托推断:
    GetAString me=x.ToString;

  3. me();或者me.Invoke();

编译器会用me.Invoke();代替me();

浅表复制,深度复制

浅表复制,成员对象,直接复制引用。成员值,创建值副本。

深度复制,成员对象,创建副本。成员值,创建值副本。

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
[Serializable]  //可被序列化,实现深度复制
public class CloneDemo // : ICloneable,可继承接口,实现方法复制
{
public int State { get; set; }
public StateObject innerObj { get; set; }

[Serializable]
public class StateObject
{
public int InnerState { get; set; }
}

public CloneDemo()
{
State = 1;
innerObj = new StateObject { InnerState = 1, };
}

//浅表复制
public CloneDemo ShadowClone()
{
return this.MemberwiseClone() as CloneDemo;
}

//深度复制
public CloneDemo DeepClone()
{
using (var stream = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Seek(0, SeekOrigin.Begin);
return formatter.Deserialize(stream) as CloneDemo;
}
}
}

class Program
{
static void Main(string[] args)
{
var demo = new CloneDemo();
var shadow = demo.ShadowClone();
var deep = demo.DeepClone();
Console.WriteLine("demo:属性State = {0},引用innerObj.State = {1}", demo.State, demo.innerObj.InnerState);
Console.WriteLine("shadow:属性State = {0},引用innerObj.State = {1}", shadow.State, shadow.innerObj.InnerState);
Console.WriteLine("deep:属性State = {0},引用innerObj.State = {1}", deep.State, deep.innerObj.InnerState);
demo.State = -1;
demo.innerObj.InnerState = -1;
Console.WriteLine("====================\n分别对属性和引用赋值...\n====================");
Console.WriteLine("demo:属性State = {0},引用innerObj.State = {1}", demo.State, demo.innerObj.InnerState);
Console.WriteLine("shadow:属性State = {0},引用innerObj.State = {1}", shadow.State, shadow.innerObj.InnerState);
Console.WriteLine("deep:属性State = {0},引用innerObj.State = {1}", deep.State, deep.innerObj.InnerState);

Console.ReadKey();
}
}

Array抽象类

1
2
3
4
Type t=...;
Array intArr=Array.CreateInstance(typeof(t),5);
intArr.SetValue(33,2);
intArr.GetValue(2);

事先不知类型,可由此创建数组。

Array.Sort(数组,IComparer 比较器);

Enumerator

GetEnumerator()方法用IEnumerable接口定义。foreach语句并不真的需要集合实现这个接口。有一个名为GetEnumerator()的方法,返回实现了IEnumerator接口的对象就行了。

IEnumerator接口定义了Reset()方法,以与COM交互操作。

foreach

解析为下面的代码段:

1
2
3
4
5
6
7
8
9
var enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
if (item != null)
{
Console.WriteLine(item.Name);
}
}

yield语句

包含yield语句的方法或属性也成为迭代块。必须声明为返回IEnumerator或IEnumerable接口。可包含yield return或yeild break,不能有return语句。

使用迭代块,编译器会生成一个yield类型,包含一个状态机。记录了迭代的当前位置。

foreach访问迭代器,一次访问一个。无需一次加载完所有数据。可以把yeild类型看作内部类Enumerator。

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 My
{
public class Enumerator : IEnumerator<string>, IEnumerator, IDisposable
{
private int state;
public string Current { get; private set; }

public void Dispose()
{
//...
}

public bool MoveNext()
{
//...
}

public void Reset()
{
//...
}

object IEnumerator.Current {get { return Current; }}
}
}

默认迭代是:定义为返回Enumerator的GetEnumerator()方法,是foreach默认采用的方法。自定义命名迭代返回IEnumerable。

集合特性

  • IEnumerabel:GetEnumerator(),返回一个实现了IEnumerator接口的枚举。可以被foreach。
  • ICollection:由泛型集合实现。Count属性。CopyTo()方法(把集合复制到数组中)。Add(),Remove(),Clear()方法。
  • IList:派生自ICollection。定义了索引器,可通过指定位置,Insert()或RemoveAt()。
  • ISet:派生自ICollection。由集实现。合并,交集,并集。
  • IDictionary:键值集合。索引器。
  • ILookup:一个键包含多个值。
  • IComparer:比较器实现。Compare()对集合中的元素排序。
  • IEqualityComparer:比较器,数组,元祖实现。该比较器可用于字典中的键。
  • IProducerConsumerCollection:.NET4.0。支持新的线程安全的集合类。
  • IReadOnlyCollection,IReadOnlyList,IReadOnlyDictionary:初始化后不能修改的,只能检索。
  • IImmutableArray,IImmutableList,IImmutableQueue,IImmutableSet
  • IImmutableDictionary:初始化后不能修改。不可变接口定义了不可变集合的方法和属性。

队列

先进先出(FIFO)容器。

Queue ,先进先出(FIFO)。
Enqueue(),一端添加元素。Dequeue(),另一端读取和删除元素。
Peek(),从头部读取一个元素,但不删除。

后进先出(LIFO)容器。

Push(),添加元素。Pop()获取最近添加的元素。
Peek(),返回栈顶的元素,但不删除它。

Lookup类

一键对多值

1
2
3
4
5
6
7
8
9
10
11
12
List<Book> list = new List<Book>
{
new Book() { Title = "b1", Price = 1 },
new Book() { Title = "b2", Price = 2 },
new Book() { Title = "a2", Price = 2 }
};

var bookLk = list.ToLookup(p => p.Price);
foreach (var item in bookLk[2])
{
Console.WriteLine(item.Title);
}

HashSet 不重复无序,SortedSet 不重复有序

1
2
3
4
5
6
aSet.Add()  //是否成功添加了元素。
aSet.IsSubsetOf(bSet) //a是否是b的子集(b包含a所有元素)
bSet.IsSupersetOf(aSet) //b是否是a的超集(b包含a所有元素)
aSet.Overlaps(bSet) //a与b是否共享某元素。(重叠)
aSet.UnionWith(bSet) //向a中加入b所有的元素。
aSet.ExceptWith(bSet) //从a中删除b拥有的元素

Immutable

不可变集合,需要引用System.Collections.Immutable(.NET4.5)

1
2
3
ImmutableList<string>.Add("")   //每次返回一个新的不变集合,不改变本身。
var build=imList.ToBuild() //构建器。返回一个可变集合。可以进行.Add(),.Remove()等。
build.ToImmutable() //返回一个变动后的不可变集合。

并发集合

  • IProducerConsumerCollection:TryAdd(),和TryTake()。
  • ConcurrentQueue:免锁定,内部链表。TryTake(),Enqueue(),TryDequeue(),TryPeek()
  • ConcurrentStack:链表。Push(),TryPeek(),TryPop(),TryPopRange()
  • ConcurrentBag:没有定义添加或提取项的任何顺序,线程映射到内部使用的数组,尝试减少锁定。
  • ConcurrentDictionary:线程安全,非阻塞键值集合。TryGetValue(),TryRemove(),TryUpdate()。没有实现IProducerConsumerCollection
  • BlockingCollection:阻塞线程。Add(),Take()。

dynamic类型

dynamic类型允许编写忽略编译期间的类型检查代码。

var对象类型的确定会延迟,确定后不可变。dynamic类型可以改变多次。int->string->object

async

只能用于返回Task或void的方法。不能作为程序的入口点。

Task

启动Task的3种方式:

1.

1
2
3
4
5
6
7
Action act = () =>
{
Console.WriteLine("任务开始");
Thread.Sleep(2000);
};
Task t = new Task(act);
t.Start();

2.

1
Task t = Task.Factory.StartNew(act);

3.

1
2
3
4
5
6
7
8
Task t = Task.Run(act);    //对Factory的封装

//完成通知,回调
t.ContinueWith(task =>
{
Console.WriteLine("完成状态");
Console.WriteLine("IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
});

Task组合器

  • Task.WhenAll(t1,t2):等待,直到全部任务都完成。
  • Task.WhenAny(t1,t2):等待,直到其中一个任务完成就会返回。

应用CancellationTokenSource,取消Task

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
class Program
{
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
Task<int> t = new Task<int>(() => Add(cts.Token), cts.Token);
t.Start();
t.ContinueWith(TaskEnded);
Console.ReadKey();
cts.Cancel();
Console.ReadKey();
}

static void TaskEnded(Task<int> task)
{
Console.WriteLine("完成");
Console.WriteLine("IsCanceled:{0},IsCompleted:{1},IsFaulted:{2}",
task.IsCanceled, task.IsCompleted, task.IsFaulted);

①Console.WriteLine("返回值:" + task.Result);
try
{
Console.WriteLine("返回值:" + task.Result);
}
catch (AggregateException e)
{
var errs = e.InnerExceptions; //与组合器配合,获取所有任务异常。
e.Handle(err => err is OperationCanceledException);
}
}

static int Add(CancellationToken token)
{
Console.WriteLine("任务开始");
int result = 0;

while (!token.IsCancellationRequested)
{
result++;
Thread.Sleep(1000);
}

②③while (true)
{
result++;
Thread.Sleep(1000);

②token.ThrowIfCancellationRequested();

if (result == 5)
{
throw new Exception("error");
}
}
return result;
}
}

Task t=… ,有返回参数才有①t.Result属性。任务完成则,IsCompleted:true。不论是否异常。

①进行逻辑判断,任务正常结束后,进入TaskEnded,结果:IsCanceled:false,IsFaulted:false 。

②通过抛AggregateException异常,立即结束任务,进入TaskEnded,结果:IsCanceled:true,IsFaulted:false。

③通过抛自定义异常,立即结束任务,进入TaskEnded,结果:IsCanceled:false,IsFaulted:true。

虚拟寻址系统

32位处理器上的每个进程都可以使用4GB的内存,无论计算机实际有多少物理内存(64位更大)。包含了:可执行代码,dll,变量等。

栈(stack)

栈指针指向为栈保留的内存块末尾。栈实际上是向下填充的。即从高内存地址向低内存地址填充。数据入栈后,指针调整,始终指向下一空闲存储单元。数据释放(变量超出作用域),指针向上递增。

变量的生存期总是嵌套的,保证了栈指针是有序的。

堆(heap)

堆上的内存是向上分配的。空闲空间在已用空间的上面。

垃圾回收

gc运行时,会从堆中删除不再引用的所有对象。只要释放了能释放的所有对象,它就把其他对象移动回堆的端部。再次形成一个连续的内存块。因此,堆可以确定在什么地方存储新对象。对象移动后,需要新地址更新引用,gc会处理更新问题。

大对象堆

不同于主堆,存储使用大于85000个字节对象。压缩大对象比较昂贵。故大对象对上的对象不执行压缩过程。

第二代和大对象堆上的回收放在后台线程执行,应用程序仅为第0代和第1代回收而阻塞,减少了暂停时间。

非托管资源

文件句柄,网络连接,数据库连接等。

托管资源

栈,堆上的数据。

析构函数

C#不常使用,无法确定执行时机。有析构函数的对象需要两次处理才能销毁。析构函数运行时间长,非常耗性能。

IDisposable接口

C#推荐IDisposable接口替代析构函数。声明Dispose(),释放非托管资源,控制更精准。

如果显式调用需要try catch,防止异常而没有执行。

代替用using()语句,更简单。

unsafe代码块

unsafe可以修饰 类成员方法等。标记为不安全代码,可以使用指针语法提高性能。但带来不安全性和不方便等。

指针语法

1
2
3
4
5
int x=10;
int* pX,pY;
pX= &x;
pY=px;
*pY=20;

x内容改为20。pY与x中间没有任何关系,pY碰巧指向存储x的存储单元。

//pX,pY也占用4个字节,因为32位处理器上,4个字节存储一个地址。

&表示取地址,把一个值类型转换为指针。

*表示获取地址的内容,把一个指针转换为值类型。

强制转换:

1
2
uint y=(uint)pX;
int* pD=(int*)y;

自定义特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[FieldName("xxx")]  //FieldNameAttribute,Attribute可以省略,自动添加。搜索指定名称的类,实例化。
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, //引用在哪些元素上,或的关系
AllowMultiple=false, //是否可以多次应用在同一元素
Inherited=false)] //自动应用到派生类或接口,重写的方法等

public class FieldNameAttribute: Attribute
{
public string Comment {get;set;}

private string name;
public FieldNameAttribute(string name)
{
this.name=name;
}
}

使用:

1
2
3
4
5
[FieldName("xxx",Comment="xxx")]
public string Func
{
//...
}

Exception

  • 属性

Data,可以添加的额外信息字典。

HelpLink,连接帮助文件上。

InnerException,如异常在catch中抛出,则inner为把代码发送到catch块的异常对象。

Source,导致异常程序或对象名。

StackTrace,调用栈信息。

TargetSite,抛出异常的方法的反射对象。 .ReflectedType获得类的Type对象。

调用者特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Book
{
public string Title { get; set; }
public decimal Price { get; set; }

public void Log([CallerLineNumber] int line = 0,
[CallerFilePath] string path = "",
[CallerMemberName] string name = "")
{
//Do with line,path,name... 包括调用者的信息
}
}

Book b = new Book();
b.Log();

ThreadPool

1
2
3
4
5
6
Book b = new Book() { Title = "t", Price = 1 };
ThreadPool.QueueUserWorkItem(i =>
{
Book b1 = i as Book;
Console.WriteLine("书名" + b1.Title + "价格" + b1.Price);
}, b);

线程池的限制

  1. 线程池中的所有线程都是后台线程。并且不能把入池的线程改为前台线程。
  2. 不能设置池中线程的优先级或名称。
  3. 所有线程都是多线程单元(MTA)线程,许多COM对象都需要单线程单元(STA)线程。
  4. 入池的线程只能用于时间短的任务。
    如果需要一直运行(如word拼写检查),应创建Thread或Task使用LongRunning选项。

控制线程

1
2
3
4
var t1 = new Thread(() => { });
t1.Priority = ThreadPriority.Highest; //优先级,优先调用。可能影响其他线程
t1.Abort(); //停止线程,抛出异常
t1.Join(2000); //阻塞

Interlocked类

速度快,简单的同步问题。原子操作。

1
2
Interlocked.CompareExchange();
Interlocked.Increment();

Monitor类

lock语句会被编译器解析成以下代码:

1
2
3
4
5
6
7
8
9
10
object obj = new object();
Monitor.Enter(obj);
try
{
//线程同步区域
}
finally
{
Monitor.Exit(obj);
}

Monitor的好处是可以指定等待时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool lockToken = false;
Monitor.TryEnter(obj, 500, ref lockToken);
if (lockToken)
{
try
{
//线程同步区域
}
finally
{
Monitor.Exit(obj);
}
}
else
{
//做其他的事情
}

读写互斥锁

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
class Program
{
private static List<int> items = new List<int>() { 0, 1, 2, 3, 4, 5, 6 };
private static ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();

static void ReaderMethod(object reader)
{
try
{
rwl.EnterReadLock(); //如果该锁是读取模式,则读取线程直接进入。累计数+1
for (int i = 0; i < items.Count; i++)
{
Console.WriteLine("reader {0},loop {1},item {2}", reader, i, items[i]);
}
}
finally
{
rwl.ExitReadLock(); //读取线程退出,累计数-1。为0时退出读取模式
}
}

static void WriterMethod(object writer)
{
try
{
//Thread.Sleep(100); //如果写入线程等待,则全部读取的线程会先执行完。否则就抢
while (!rwl.TryEnterWriteLock(50)) //如果取得写入锁,则进入独占写入模式。
{
Console.WriteLine("writer {0} 等待取写入锁", writer);
Console.WriteLine("当前读者个数" + rwl.CurrentReadCount);
}
Console.WriteLine("writer {0} 取得写锁", writer);
for (int i = 0; i < items.Count; i++)
{
items[i]++;
Thread.Sleep(50);
}
Console.WriteLine("writer {0} 写入完毕", writer);
}
finally
{
rwl.ExitWriteLock(); //退出独占写入模式,读写线程开始抢锁
}
}

static void Main(string[] args)
{
var taskFac = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
for (int i = 0; i < 6; i++)
{
if (i==1 || i==4)
{
taskFac.StartNew(WriterMethod, i);
}
else
{
taskFac.StartNew(ReaderMethod, i);
}
}
Console.ReadKey();
}
}
欢迎打赏