C#高级编程_注释笔记

Type类型

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

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

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的成员方法

GetConstructor[s]()     //返回ConstructorInfo
GetEvent[s]()           //返回EventInfo
GetField[s]()           //返回FieldInfo
Get[Default]Member[s]() //返回MemberInfo
GetMethod[s]()          //返回MethodInfo
GetProperty[s]()        //返回PropertyInfo

Assembly和Type

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. 文化

应用程序域

图解

应用程序域

不同域加载程序集

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();
        }
    }
}

当前域加载程序集

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

方法和函数

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

构造函数初始化器

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();

浅表复制,深度复制

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

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

[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抽象类

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

解析为下面的代码段:

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。

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类

一键对多值

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 不重复有序

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)

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. 
Action act = () =>
{
    Console.WriteLine("任务开始");
    Thread.Sleep(2000);
};
Task t = new Task(act);
t.Start();

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

3. 
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

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;
}

PS:
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可以修饰 类成员方法等。标记为不安全代码,可以使用指针语法提高性能。但带来不安全性和不方便等。

指针语法

int x=10;
int*  pX,pY;
pX= &x;
pY=px;
*pY=20;

结果:x内容改为20。pY与x中间没有任何关系,pY碰巧指向存储x的存储单元。
//pX,pY也占用4个字节,因为32位处理器上,4个字节存储一个地址。
&表示取地址,把一个值类型转换为指针。
*表示获取地址的内容,把一个指针转换为值类型。

  • 强制转换:

    uint y=(uint)pX;
    int pD=(int)y;

自定义特性

[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;  
   }
}

使用:

[FieldName("xxx",Comment="xxx")]
public string Func
{ 
    ... 
}

Exception

  • 属性
    Data,可以添加的额外信息字典。
    HelpLink,连接帮助文件上。
    InnerException,如异常在catch中抛出,则inner为把代码发送到catch块的异常对象。
    Source,导致异常程序或对象名。
    StackTrace,调用栈信息。
    TargetSite,抛出异常的方法的反射对象。 .ReflectedType获得类的Type对象。

调用者特性

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

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选项。

控制线程

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

Interlocked类

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

Interlocked.CompareExchange();
Interlocked.Increment();

Monitor类

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

object obj = new object();
Monitor.Enter(obj);
try
{
    //线程同步区域
}
finally
{
    Monitor.Exit(obj);
}

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

bool lockToken = false;
Monitor.TryEnter(obj, 500, ref lockToken);
if (lockToken)
{
    try
    {
        //线程同步区域
    }
    finally
    {
        Monitor.Exit(obj);
    }
}
else
{
    //做其他的事情
}

读写互斥锁

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();
    }
}
欢迎打赏