读CLR via C#笔记

1、is 和 as 的区别

public class Employee
{ }

a):

object obj = new Employee();
if (obj is Employee)
{
    Employee e = (Employee)obj;
    //do something....
}

b):

object obj = new Employee();
Employee e = obj as Employee;
if (e != null)
{
    //do something...
}

以上a和b的实现效果是一样的,但是在CLR是运行性能却是b的高,因为CLR是类型安全的,在写法a中需要做2次类型安全检查,obj is Employee做一次安全检查,Employee e = (Employee)obj;此时再做一次安全检查,而写法b中Employee e = obj as Employee;只是做了一次类型安全检查。

2、C#中所有的int类型均是32位的,始终映射到Int32,无论操作系统是32位还是64位。
3、C#中的long类型映射到Int64.
4、C#中float类型的数值转换为int类型时,会向上取整,例如float类型的6.8转换为int类型时,得出的值为6,而不是7.
5、Decimal类型的运算性能低于其它基元(primitive)类型,BigInteger永远不会抛出OverflowException,如果数值太大,则会抛出OutOfMemoryException。
6、对象声明为值类型的前提:类型的实例比较小(小于16字节)或类型的实例比较大(大于16字节)但不作为方法实参传递,也不从方法返回。
7、应该尽量使用泛型类型的集合,少使用非泛型,便如应该尽量使用List<T>,而少使用ArrayList类型,在对值类型的集合操作时,泛型类型可以不需要装箱和拆箱的操作,而非泛型集合需要有装箱和拆箱的操作,进而减少垃圾回收次数,泛型类型能大大提高性能。
8、装箱是指CLR将值类型的实例对应的内容复制到托管堆上,然后返回托管堆上的地址指针。拆箱是指CLR将引用类型在托管堆上的数据复制到线程堆栈上。
9、如果一个程序集想访问另一个程序集的internal声明的类或方法,可以使用友元,如:有2个程序集Microsoft.dll,Othersoft.dll,Othersoft.dll想让Microsoft.dll访问Othersoft.dll中声明为internal的类型或方法,可以在Othersoft.dll的命名空间上对Microsoft.dll声明为友元。如下代码:

using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft")]
namespace Othersoft
{   
    internal class OthersoftGreeting
    {
        internal void SayHello()
        {
            Console.WriteLine("SayHello from Othersoft.");
        }
    }
}

InternalsVisibleTo类的参数为完整的程序集名称,签名和版本号可选。
这样在Microsoft.dll中的类中就可以直接使用Othersoft.dll中的OthersoftGreeting类和其中的方法。

namespace Microsoft
{
    public class MicrosoftUtil
    {
        public void SayHello()
        {
            Othersoft.OthersoftGreeting greeting = new Othersoft.OthersoftGreeting();
            greeting.SayHello();
        }
    }
}

10、做类型设计时,要尽量减少虚方法的数量,调用虚方法的速度比调用非虚方法慢。

11、使用常量(const)的风险,当一个程序集中含有常量变量时,另一个程序集对这个含有常量程序集的引用时,常量被作为元数据存入了程序集,如果常量程序集中常量的值发生了变更,而引用的程序集不重新编译,这样常量的值还是变更之前的值,引用的程序集只有重新编译才能更新常量的值。

12、readonly字段只能在类型的构造函数中赋值,其它地方不允许修改,但是可以通过反射的方式修改。

13、readonly声明的字段,不能改变的是这个字段指向指针,而不是不能改变指针指向的地址的值,如:

class Program
{

        static readonly char[] greeting = new char[] {‘H‘,‘E‘,‘L‘,‘L‘,‘O‘,‘,‘,‘W‘,‘O‘,‘R‘,‘L‘,‘D‘,‘!‘ };
        
        static void Main(string[] args)
        {
            Console.WriteLine(greeting);

            for (int i = 0; i < greeting.Length; i++)
            {
                greeting[i] = ‘A‘;
            }

            Console.WriteLine(greeting);
}

上面的代码是合法的,并且能正常输出:
HELLO,WORLD!
AAAAAAAAAAAA

但是如果添加以下代码:
greeting = new char[] { ‘A‘,‘A‘,‘A‘};
这样编译器将无法编译。

14、结构不允许定义无参构造函数,但是可以在有参数构造函数中用this 调用无参数构造函数,在结构的构造函数中,必须保证结构的所有变量都被初始化。

struct Point
{
   private Int32 m_x, m_y;
   public Point(Int32 x)
   {
      m_x = x;
   }
}

上面的代码将无法正常编译,会提示Point.m_y必须在返回前被赋值。

struct Point
{
    private Int32 m_x, m_y;
    public Point(Int32 x)
    {
        this = new Point();
        m_x = x;
    }
}

上面的代码是能正常编译执行的,用this调用无参构造函数,这样保证所有的变量都被赋初始值。

15、扩展方法类必须声明为非泛型且为static,方法必须声明为static,访问扩展方法时,需要对扩展方法命名空间添加using引用 。
16、分部(partial)方法必须声明在分部(partial)类中,并且分部方法必须为private,如果分部方法没有方法体,则对分部方法的调用IL会自动忽略。
17、声明含有默认值参数的方法,有默认值的参数要放在没有默认值的参数之后,在调用时,可以显示指定传入参数的值,不传值的参数使用默认值,如:

private static void MethodWithDefault(int x = 5, string s = "A",
    DateTime dt = default(DateTime), Guid guid = new Guid())
{
    Console.WriteLine("x = {0}, s = {1}, dt = {2}, guid = {3}",
        x, s, dt, guid);
}

调用:

int n = 0;
MethodWithDefault();
MethodWithDefault(n++);
MethodWithDefault(n++, n++.ToString());
MethodWithDefault(n++, n++.ToString(), DateTime.Now);
MethodWithDefault(x: n++, guid: Guid.NewGuid());

输出:

x = 5, s = A, dt = 0001-01-01 00:00:00, guid = 00000000-0000-0000-0000-000000000000
x = 0, s = A, dt = 0001-01-01 00:00:00, guid = 00000000-0000-0000-0000-000000000000
x = 1, s = 2, dt = 0001-01-01 00:00:00, guid = 00000000-0000-0000-0000-000000000000
x = 3, s = 4, dt = 2016-03-25 10:58:46, guid = 00000000-0000-0000-0000-000000000000
x = 5, s = A, dt = 0001-01-01 00:00:00, guid = 6e2564bc-9202-4c95-aec8-97568706846d

18、在方法参数中,将大的值类型参数声明为out时,可以提高代码的执行效率,因为它避免了在进行方法调用时复制值类型实例的字段,out和ref声明的参数,在IL中生成的代码是相同的,都是传递参数的指针。
19、如果方法的参数用params声明不定长参数,则CLR会自动给该方法添加ParamArrayAttribute属性,在运行时,如果无法匹配对应参数的方法,CLR会去有ParamArrayAttribute属性声明的方法中匹配。
20、方法中参数的类型最好使用弱类型,而少用强类型,如:

public void ProcessBytes(Stream stream)
{ }

public void ProcessBytes(FileStream fs)
{ }

参数为Stream类型的要比FileStream的好,参数为Stream类型的方法可以处理FileStream、NetworkStream和MemoryStream等,而参数类型为FileStream的只能处理FileStream。
相反,方法的返回类型最好是声明成强类型而非弱类型。

21、不要使用任何MashalByRefObject派生的类。

22、匿名类型可以用于临时声明需要不同数据类型集合的组合体,如:

var person = new { Name = "Zhangsan", Age = 32 };
    Console.WriteLine(string.Format("Name:{0},Age:{1}", person.Name, person.Age));

上面声明了一个含有2个字段(Name,Age)的类型,属性均是只读的,匿名类型仅用于方法内部。

23、dynamic配合ExpandoObject生成对象,可以很方便的为对象添加任意类型的字段和类型,对象的使用可以类型JavaScript中对象的使用,编译器不提供智能感知,可以用IDictionary<String, Object>泛型强制类型转化。

dynamic expando = new ExpandoObject();
expando.Name = "Zhangsan";
expando.Age = 32;
expando.Id = "0001";

Console.WriteLine(string.Format("Name:{0},Age:{1},Id:{2}", expando.Name, expando.Age, expando.Id));
    ((INotifyPropertyChanged)expando).PropertyChanged += (s, ee) => {
     Console.WriteLine("Property:{0} value changed.", ee.PropertyName);
    };
    expando.Name = "Lisi";
    foreach (var pair in (IDictionary<string,Object>)expando)
    {
        Console.WriteLine(string.Format("{0}:{1}", pair.Key, pair.Value));
    }

以上代码输出结果:

Name:Zhangsan,Age:32
    Name:Zhangsan,Age:32,Id:0001
    Property:Name value changed.
    Name:Lisi
    Age:32
    Id:0001

24、属性在程序发布时,编译器会将属性声明为内联方法,在调试模式时不会声明为内联方法,内联方法比普通方法的执行效率要高,而无论是调试模式还是发布后的程序,字段均执行效率高。
25、所有的事件最好声明返回类型为void,事件在触发时,有可能事件的订阅者此时取消订阅,那么就需要对事件的订阅做同步操作,访问报NullReferenceException,此时比较好的做法是用Volatile.Read()的方式去获取事件的临时拷贝,如下代码:

public delegate void OnNewMailDelegate(object sender, EventArgs e);
    public static event OnNewMailDelegate OnNewMail;
    if (OnNewMail != null)
    {
        OnNewMailDelegate temp = Volatile.Read(ref OnNewMail);
        if (temp != null)
        {
            temp(null, null);
        }
    }

26、CLR允许创建引用泛型类型和值泛型类型,但不允许创建枚举泛型类型。
27、优先使用泛型类型,泛型类型性能更优。
28、泛型的主要约束,可以为泛型指定约束,但是约束不能为以下几种特殊类型:System.Object,System.Array , System.Delegate , System.MulticastDelegate ,System.ValueType , System.Enum , System.Void。
如,引用 类型约束,如果不声明为引用类型约束将无法编译,提示类型不能设置为null:

public sealed class ReferenceCheck<T> where T: class
    {
        private T m_instance;
        public ReferenceCheck()
        {
            m_instance = null;
        }
    }

值类型约束,如果不声明为值类型约束,将不能使用T()默认构造函数。

public sealed class ValueCheck<T> where T : struct
    {
        private T m_instance;
        public ValueCheck()
        {
            m_instance = T();
        }
    }

次要约束,指定T类型必须能转化为TBase类型。

public static List<TBase> ConvertIList<T, TBase>(IList<T> list) where T : TBase
        {
            List<TBase> baseList = new List<TBase>();
            if (list != null && list.Count > 0)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    baseList.Add(list[i]);
                }
            }
            return baseList;
        }

29、和引用类型类似,值 类型可以实现零个或多个接口,但值类型在转换为接口时需要装箱, 这是由于接口是引用,必须指向堆上的对象,使CLR能检查对象的类型的对象指针,从而判断对象的确切类型,调用已装箱值类型的接口方法时,CLR会跟随对象的类型对象指针找到类型对象的方法表,从而正确调用方法。
30、CLR根据类型的对象指针找到对象的方法表,如果方法表中找不到需要调用的方法,会向对象的基类型的方法表中查找,直到找到为止,如果无法找到将会报方法不存在的异常。多态的实现正是基于此,如果子类重写了(new)父类的方法,在用父类声明子类的实例时,会直接调用父类的方法,如果需要调用子类的方法,可以用子类做强制类型转化调用,而子类覆盖了(override)了父类的方法,会直接调用子类的方法。
31、类型只能继承一个实现,如果派生类型不能和基类型建立起IS A关系,就不用基类,而用接口,接口意味着CAN DO关系。
32、换行符最好使用System.Enviroment.NewLine常量,这样可以保证在所有的地方都能正确运行。
33、字符串比较时,应该总是使用StringComparison.Ordinal或者StringComparison.OrdinalIgnoreCase,忽略语言文化的字符串比较是最快的方式,字符串比较时应该使用String的ToUpperInvariant或ToLowerInvariant方法,ToUpper和ToLower对文化敏感。

34、SecureString可以用来处理敏感字符串,字符串在托管堆中是加密的,如果需要解密需要用非托管代码来处理,如:

using (SecureString sc = new SecureString())
            {
                Console.Write("Please input your password:");
                while (true)
                {   
                    ConsoleKeyInfo keyInfo = Console.ReadKey(true);
                    if (keyInfo.Key == ConsoleKey.Enter)
                    {
                        break;
                    }
                    sc.AppendChar(keyInfo.KeyChar);
                    Console.Write("*");
                }
                Console.Write(Environment.NewLine);
                DisplayPassword(sc);
            }

      private unsafe static void DisplayPassword(SecureString sc)
        {
            Console.Write("Your password is:");
            char* pc = null;
            try
            {
                pc = (char*)Marshal.SecureStringToCoTaskMemUnicode(sc);
                for (int i = 0; pc[i] != 0; i++)
                    Console.Write(pc[i]);
            }
            catch
            { }
            finally
            {
                if (pc != null)
                    Marshal.ZeroFreeCoTaskMemUnicode((IntPtr)pc);
            }
            Console.Write(Environment.NewLine);
        }

35、用ToString("X")输出枚举的16进制字符串时,输出的字符是几位数,由枚举的类型确定,如果枚举是byte/sbyte则输出2位数,short/unshort则输出4位数,int/unint则输出8位数,long/unlong则输出16位数,如果有必要会在前面补0.

enum ByteColor : byte
      { 
            White,
            Green,
            Blue,
            Yellow,
            Orange
        }

        enum IntColor : int
        {
            White,
            Green,
            Blue,
            Yellow,
            Orange
        }

        enum LongColor : long
        {
            White,
            Green,
            Blue,
            Yellow,
            Orange
        }

         ByteColor bColor = ByteColor.Blue;
            IntColor iColor = IntColor.Blue;
            LongColor lColor = LongColor.Blue;
            Console.WriteLine(string.Format("Byte:{0}", bColor.ToString("X")));
            Console.WriteLine(string.Format("Int:{0}", iColor.ToString("X")));
            Console.WriteLine(string.Format("Long:{0}", lColor.ToString("X")));

输出:

Byte:02
    Int:00000002
    Long:0000000000000002

36、如果只是需要将数组的某些元素复制到另一个数组,可选择System.Buffer.BlockCopy方法,它比Array.Copy方法快,但是Buffer.BlockCopy只支持基元类型,不提供像Array.Copy提供转型能力。要将一个数组元素可靠的复制到另一个数组,应该使用Array.ConstainedCopy方法,该方法要么完成复制,要么抛出异常,总之不会破坏目标数组中的数据。

37、声明的方法,如果返回类型为数组,如果返回值没有任何元素,建议返回一个含有0个元素的数组,而不要返回null,因为这样可以简化调用者的代码,无须做null判断。

38、大对象(大于85000)直接存放在大对象堆中(LOH),GC代数直接为2代,可以通过GC.GetGeneration(obj)的方式获取对象在GC中的代数。

39、声明一个委托类型,其实就是声明了一个类,所以委托可以声明在任何地方,在不同的地方有不同的访问级别,委托内有3个成员_target(Object类型,当委托对象包装一个静态方法时,这个字段为null,当委托对象包装一个实例方法时,这个字段引用的是回调方法要操作的对象,换言之,这个字段指出要传给实例方法的隐匿this的值)、_methodPtr(IntPtr类型,指向回调方法的地址)、_invocationList(Object类型,通常为null,构造委托链时它引用 一个委托数组,Delegate.Combine()方法添加委托链),委托对象的+=,-=实际上就是调用Delegate.Combine()和Delegate.Remove()方法。

40、尽量少声明委托,因为系统已经提供了足够多的原始类型的委托,无返回类型的委托有
Action(),Action<T>(T arg),Action<T1,T2>(T1 arg1,T2 arg2)......Action<T1,T2....T16>(T1 arg1,T2 arg2,...T16,arg16)这16种类型的委托基本上已经适用大多数的情况,很少有需要再声明其它委托。
有返回类型的委托有:
TResult Func<TResult>(),TResult Func<T1,TResult>(T1 arg1),TResult Func<T1,T2,TResult>(T1 arg1,T2 arg2),....TResult Func<T1,T2,...T16,TResult>(T1 arg1,T2 arg2,....T16 arg16),这16种类型的有返回值的委托类型基本上能满足平时开发中的大多数情况。需要自定义委托的情况:
1)需要使用ref,out传递参数
2)需要使用params传递可变数量的参数

41、自定义Attribute通过重载Match和Equals方法,可以用于同样的类型的实例,执行同样的方法,拥有不同的输出结果,如下示例:

enum Accounts
        {
            Savings = 0x0001,
            Checking = 0x0002,
            Brokerage = 0x0004
        }

      [AttributeUsage(AttributeTargets.Class)]
        class AccountsAttribute : Attribute
        {
            private Accounts m_accounts;

            public AccountsAttribute(Accounts accounts)
            {
                this.m_accounts = accounts;
            }

            public override bool Match(object obj)
            {
                if (obj == null) return false;

                if (obj.GetType() != this.GetType()) return false;

                AccountsAttribute other = (AccountsAttribute)obj;
                if ((other.m_accounts & m_accounts) != m_accounts) return false;

                return true;
            }

            public override bool Equals(object obj)
            {
                if (obj == null) return false;

                if (obj.GetType() != m_accounts.GetType()) return false;

                AccountsAttribute other = (AccountsAttribute)obj;
                if ((other.m_accounts & m_accounts) != m_accounts) return false;

                return true;
            }

            public override int GetHashCode()
            {
                return (int)m_accounts;
            }

        }

      [Accounts(Accounts.Savings)]
        class ChildAccount
        { }

        [Accounts(Accounts.Savings | Accounts.Checking | Accounts.Brokerage)]
        class AdultAccount
        { }

      private static void CanWriteCheck(Object obj)
        {
            Type type = obj.GetType();
            AccountsAttribute checking = new AccountsAttribute(Accounts.Checking);
            Attribute validAttribute = Attribute.GetCustomAttribute(obj.GetType(), typeof(AccountsAttribute), false);

            if (checking.Match(validAttribute))
            {
                Console.WriteLine(string.Format("{0} types can wirte checks.", obj.GetType()));
            }
            else
            {
                Console.WriteLine(string.Format("{0} types can not wirte checks.", obj.GetType()));
            }
           
        }

         ChildAccount child = new ChildAccount();
            AdultAccount adult = new AdultAccount();
            CanWriteCheck(child);
            CanWriteCheck(adult);
            CanWriteCheck(new Program());
enum Accounts
        {
            Savings = 0x0001,
            Checking = 0x0002,
            Brokerage = 0x0004
        }

      [AttributeUsage(AttributeTargets.Class)]
        class AccountsAttribute : Attribute
        {
            private Accounts m_accounts;

            public AccountsAttribute(Accounts accounts)
            {
                this.m_accounts = accounts;
            }

            public override bool Match(object obj)
            {
                if (obj == null) return false;

                if (obj.GetType() != this.GetType()) return false;

                AccountsAttribute other = (AccountsAttribute)obj;
                if ((other.m_accounts & m_accounts) != m_accounts) return false;

                return true;
            }

            public override bool Equals(object obj)
            {
                if (obj == null) return false;

                if (obj.GetType() != m_accounts.GetType()) return false;

                AccountsAttribute other = (AccountsAttribute)obj;
                if ((other.m_accounts & m_accounts) != m_accounts) return false;

                return true;
            }

            public override int GetHashCode()
            {
                return (int)m_accounts;
            }

        }

      [Accounts(Accounts.Savings)]
        class ChildAccount
        { }

        [Accounts(Accounts.Savings | Accounts.Checking | Accounts.Brokerage)]
        class AdultAccount
        { }

      private static void CanWriteCheck(Object obj)
        {
            Type type = obj.GetType();
            AccountsAttribute checking = new AccountsAttribute(Accounts.Checking);
            Attribute validAttribute = Attribute.GetCustomAttribute(obj.GetType(), typeof(AccountsAttribute), false);

            if (checking.Match(validAttribute))
            {
                Console.WriteLine(string.Format("{0} types can wirte checks.", obj.GetType()));
            }
            else
            {
                Console.WriteLine(string.Format("{0} types can not wirte checks.", obj.GetType()));
            }
           
        }

         ChildAccount child = new ChildAccount();
            AdultAccount adult = new AdultAccount();
            CanWriteCheck(child);
            CanWriteCheck(adult);
            CanWriteCheck(new Program());

输出如下结果:

Test.Program+ChildAccount types can not wirte checks.
Test.Program+AdultAccount types can wirte checks.
Test.Program types can not wirte checks.

42、每个AppDomain都有2个堆,一个是GC堆,一个是Loader堆,GC堆用于存放引用类型的实例,也就是会被垃圾回收机制照顾到的东西,Loader堆用于存放类型的元数据,也就是所谓的类型对象,在每个类型对象的结尾都含有一个方法表。

43、RuntimeHelpers.PrepareConstrainedRegions();是一个很特殊的方法,JIT编译器如果在try块之前发现调用了这个方法,就会提交编译try块关联中的catch和finally中的代码,JIT会加载任何程序集,创建任何类型对象,调用任何静态构造函数,并对任何方法进行JIT编译,如果任何操作发生异常,这个异常会在线程进入try块之前发生。

44、GC回收机制:当系统需要新生成一个对象时,CLR发现当前可用空间不足以分配一个新的对象,会暂停当前所有的线程,将0代堆中所有根对象的同步索引字段的位置为0,然后检查0代堆中所有的根对象,如果根对象有引用其它对象,则将这个对象的同步索引字段位置为1,同时将引用对象的同步索引位置为1,如果发现对象没有任何引用,则继续检查下一个根,直到所有的根对象检查完毕,检查完毕后,所有的根对象都要么标记为0,要么标记为1,这样标记为0的对象就是需要回收的对象,这样对象被回收后,GC会压缩空间(非真正的压缩,只是对象位移),让所有对象的存储连续,将所有幸存的对象的地址位移,代数升为1代,同理,如果0代堆回收完后,如果空间还不足以分配,则会继续回收1代堆,如果1代回收后,还不足以分配,则再回收2代堆,2代堆如果回收后还不足以分配,则会报OutOfMemoryException的异常。

45、GC有2种模式,一种是工作站模式,工作站模式针对客户端应用程序优化,GC造成的延时很低,应用程序线程挂起时间很短,避免用户感到焦虑,在该模式下,GC假定其它应用程序都不会消耗太多的CPU资源,另一种是服务器模式服务器模式,该模式针对服务器端应用程序优化GC,被优化的主要是吞吐量和资源利用。GC假定机器上没有运行其它应用程序(无论客户端还是服务端程序),并假定机器的所有CPU都可以用来辅助完成GC,该模式造成托管堆被拆分成几个区域,每个CPU一个,开始垃圾回收时,垃圾回收器在每个CPU上都运行一个特殊的线程,每个线程都和其它线程并发回收它自己的区域,这个功能要求应用程序在多CPU上计息运行,使线程能真正的工作,从而获得性能提升。
应用程序默认以工作站模式运行。

使用以下配置启用服务器模式:

<configuration>
  <runtime>
    <gcServer enabled ="true"></gcServer>
  </runtime>
</configuration>

除了这2种模式,GC还有2种子模式,并发(默认)或非并发,在并发方式 中,垃圾回收器有一个额外的后台线程,它能在应用程序运行时并发标记对象,一个线程因为分配对象造成第0代超出预算时,GC首先挂机所有线程,再判断要回收哪代,如果回收第0代或第1代,那么一切如常进行,但是如果回收第2代,就会增大第0代的大小(超过其预算),以便在第0代中分配新对象,然后应用程序的线程恢复运行。
可以使用以下配置配置启用并发模式:

<configuration>
  <runtime>
    <gcConcurrent enabled="true"></gcConcurrent>
  </runtime>
</configuration>

46、当一个对象声明析构函数时(在IL体现为Finalize方法),在对象被GC回收时,不是立即释放对象占用的资源,而时将该对象放到freachable队列中,此时该对象的同步索引位的标志位由0被标记为1(对象复活),对象被提升为1代对象,freachable队列会有一个专门的线程监控,如果队列不会空,线程则会调用freachable队列中的对象的finalize方法,释放资源,此时对象才真正的没有引用,下次垃圾回收时将回收,所以不会随意的给对象声明析构函数,这样可能影响GC的性能,造成对象需要二次GC才能回收。

47、CLR为每个AppDomain分配了一个GC Handle Table,可以通过GCHandle来控制对象的生存周期,可以通过GCHandle强制对象不被GC回收、不被压缩等操作。

48、跨AppDomain传值,一个进程中可以加载多个AppDomain,并且每个AppDomain之间是互相隔离的,如果需要不同的AppDomain传递对象,则需要要求被传递的对象是从MarshalByRefObject派生,或者是声明对象为可序列化(Serializable),从MarshalByRefObject派生的对象,跨AppDomain传递时,传递的是一个代理对象,该对象是用GCHandle封装,在调用实际对象时,代理对象会调用GCHandle里面的Target实际地址的代码,声明为可序列化的对象在传递时,是传递的实际值,已经与生成对象的AppDomain没有任何关系,传递的对象如果未从MarshalByRefObject派生或未声明为可序列化,在传递时CLR将抛出对象未被声明为可序列化。

示例:

public sealed class MarshalByRefType : MarshalByRefObject
    {
        public MarshalByRefType()
        {
            Console.WriteLine("{0} ctolr running in {0}.", this.GetType().FullName,
            Thread.GetDomain().FriendlyName);
        }

        public void DoSomthing()
        {
            Console.WriteLine("Executing in {0}.", Thread.GetDomain().FriendlyName);
        }

        public MarshalByValueType MethodWithReturn()
        {
            Console.WriteLine("Executing in {0}.", Thread.GetDomain().FriendlyName);
            MarshalByValueType obj = new MarshalByValueType();
            return obj;
        }

        public NonMarshalableType MethodArgAndReturn(string callingDomainName)
        {
            Console.WriteLine("Calling from {0} to {1}", callingDomainName,
                Thread.GetDomain().FriendlyName);
            NonMarshalableType obj = new NonMarshalableType();
            return obj;
        }
    }

    [Serializable]
    public sealed class MarshalByValueType : Object
    {
        private DateTime m_createTime = DateTime.Now;
        public MarshalByValueType()
        {
            Console.WriteLine("{0} ctor running in {1},Created on{2:D}.",
                this.GetType().FullName, Thread.GetDomain().FriendlyName, m_createTime);
        }
        public override string ToString()
        {
            return m_createTime.ToLongTimeString();
        }

    }

    public sealed class NonMarshalableType : Object
    {
        public NonMarshalableType()
        {
            Console.WriteLine("Executing in {0}.", Thread.GetDomain().FriendlyName);
        }
    }

AppDomain defaultDomain = Thread.GetDomain();
            Console.WriteLine("Default AppDomain‘s Friendly name is:{0}", defaultDomain.FriendlyName);
            string exeAssembly = Assembly.GetEntryAssembly().FullName;
            Console.WriteLine("Main assembly is:{0}", exeAssembly);
            AppDomain ad2 = null;
            Console.WriteLine("{0} Demo #1", Environment.NewLine);
            ad2 = AppDomain.CreateDomain("Ad #2", null, null);
            MarshalByRefType mbrt = null;
            mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "Test.MarshalByRefType");
            Console.WriteLine("Type={0}", mbrt.GetType());
            Console.WriteLine("Is Proxy = {0}", System.Runtime.Remoting.RemotingServices.IsTransparentProxy(mbrt));
            mbrt.DoSomthing();
            AppDomain.Unload(ad2);
            try
            {
                mbrt.DoSomthing();
                Console.WriteLine("Successful call.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed call.");
            }

            Console.WriteLine("{0} Demo #2", Environment.NewLine);
            ad2 = AppDomain.CreateDomain("Ad #2", null, null);
            mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "Test.MarshalByRefType");
            MarshalByValueType mbvt = mbrt.MethodWithReturn();
            Console.WriteLine("Is Proxy = {0}", System.Runtime.Remoting.RemotingServices.IsTransparentProxy(mbrt));
            Console.WriteLine("Return object created {0}", mbvt.ToString());
            AppDomain.Unload(ad2);
            try
            {
                Console.WriteLine("Return object created {0}", mbvt.ToString());
                Console.WriteLine("Successful call.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed call.");
            }

            Console.WriteLine("{0} Demo #3", Environment.NewLine);
            ad2 = AppDomain.CreateDomain("Ad #2", null, null);
            mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "Test.MarshalByRefType");
            NonMarshalableType nmt = mbrt.MethodArgAndReturn(defaultDomain.FriendlyName);

输出:

Default AppDomain‘s Friendly name is:Test.exe
Main assembly is:Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

 Demo #1
Test.MarshalByRefType ctolr running in Test.MarshalByRefType.
Type=Test.MarshalByRefType
Is Proxy = True
Executing in Ad #2.
Failed call.

 Demo #2
Test.MarshalByRefType ctolr running in Test.MarshalByRefType.
Executing in Ad #2.
Test.MarshalByValueType ctor running in Ad #2,Created onWednesday, April 27, 201
6.
Is Proxy = True
Return object created 15:09:38
Return object created 15:09:38
Successful call.

 Demo #3
Test.MarshalByRefType ctolr running in Test.MarshalByRefType.
Calling from Test.exe to Ad #2
Executing in Ad #2.

未经处理的异常:  System.Runtime.Serialization.SerializationException: 程序集“Te
st, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”中的类型“Test.NonMar
shalableType”未标记为可序列化。
   在 Test.MarshalByRefType.MethodArgAndReturn(String callingDomainName)
   在 Test.Program.Main(String[] args) 位置 z:\文稿\Visual Studio 2013\ProjectsSample\Test\Program.cs:行号 312

49、控制台UI程序、NT Service应用程序、Window窗体应用程序、WPF应用程序都是自寄宿(Self-hosted即自己容纳CLR)应用程序,他们都有托管EXE文件,Windows用控管EXE文件初始化进程时,会加载垫片(一般名称为mscoree.dll),垫片会根据EXE文件中的CLR头文件信息判断使用哪个版本的CLR,把CLR加载进进程后,CLR会查找Main方法,然后调用Main方法,这样应用程序才真正的运行起来。

50、反射创建对象时,除了数组和委托不能使用Activtor.CreateInstance方法创建外,其它对角均能使用该方法创建,数组需要使用Array.CreateInstance,创建委托则需要使用MethodInfo.CreateDelegate方法。构造泛型类型的实例,需要首先获取开放类型的引用 ,然后再调用Type的MakeGenericType方法,并向其传递一个数组,如:

//以下代码生成一个Dictionary<string,int>类型
Type openType = typeof(Dictionary<,>);
Type closedType = openType.MakeyGenericType(typeof(string),typeof(int32));
object obj = Activtor.CreateInstance(closedType);
Console.WriteLine(obj.GetType());

程序输出:Dictionary~2[System.String,System.Int32]

51、可以使用序列化和反序列化来实现对象的深拷贝,如下代码:

private static object DeepClone(object original)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Context = new StreamingContext(StreamingContextStates.Clone);
                formatter.Serialize(ms, original);
                //重置流的位置为起始位置
                ms.Position = 0;
                return formatter.Deserialize(ms);
            }
        }

52、如果需要使类型可序列化,需要给对象声明Serializable特性,在类型里面,如果有不想被序列化的属性,可以用NonSerialized特性来声明,如果类型发生版本变更,应该将新增的字段设置为OptionalField特性,防止反序列化时流中没有该字段而抛出SerializableException的异常。

53、可以给对象中的方法声明OnSerializing、OnSerialized,OnDeSerializing,OnDeSerialized特性来声明方法,用于在对象序序列化时(OnSerializing)、序列化为(OnSerialized)、反序列化时(OnDeSerializing)、反序列化后(OnDeserialized)改变对象的属性,在序列化时格式化器首先调用标记了OnSerializing声明的方法,然后再序列化,序列化完成,再调用OnSerialized声明的方法。反序列化时格式化器首先调用OnDeserializing声明的方法,然后反序列化,反序列化完成后,再调用OnSerialized声明的方法。

[Serializable]
        public sealed class Circle
        {
            private double m_radius;
            [NonSerialized]
            private double m_area;

            public Circle(double raidus)
            {
                this.m_radius = raidus;
            }

            [OnSerializing]
            private void Serializing(StreamingContext context)
            {
                //此处可以添加在序列化时需要做的运算
                Console.WriteLine("On Serializing.");
            }

            [OnSerialized]
            private void Serialized(StreamingContext context)
            {
                //此处可以添加在序列化完成时做的运算。
                Console.WriteLine("After serialization.");
            }

            [OnDeserializing]
            private void Deserializing(StreamingContext context)
            {
                //此处可以添加在反序列化时做的运算
                Console.WriteLine("On Deserializing.");
            }
            [OnDeserialized]
            private void Deserialized(StreamingContext contex)
            {
                //此处可以添加在反序列化完成后做的运算,比如计算圆的面积
                this.m_area = Math.PI * m_radius * m_radius;
                Console.WriteLine("After Deserialization.");
            }

            public override string ToString()
            {
                return string.Format("Circle‘s area is:{0} where radius is:{1}.", Math.PI * m_radius * m_radius, m_radius);
            }
        }

54、序列化和反序列化的过程。
序列化:
  为了简化格式化器的操作,FCL在System.Runtime.Serialization命名空间提供了一个FormatterServices类型,该类型只包含了静态方法,且不能实例化,以下步骤描述了格式化器如何自动序列化应用了SerializableAttribute特性的对象
  1、格式化器调用FormatterServices的GetSerialiableMembers方法:
  

public static MemberInfo [] GetSErializableMembers(Type type,StreamingContext context);

  这个方法利用反射获取类型的public和private实例字段(标记了NonSerializedAttribute特性的除外)。方法返回由MemberInfo对象构成的数组,其中每个元素都对应一个可序列化的实例字段。
  2、对象被序列化,System.Reflection.MemberInfo对象数组传递给FormatterServices的静态方法GetObjectData:

public static object[] GetObjectData(Object obj,MemberInfo [] members);

  这个方法返回一个object数组,其中每个元素都标识了被序死化的对象中的一个字段的值。这个object数组和MemberInfo数组是并行的,换方之,object数组中的元素0是MemberInfo数组中元素0所标识的那个成员的值。
  3、格式化器将程序集标识和类型的完整名写入流中。
  4、格式化器然后遍历2个数组中的元素,将每个成员的名称和值写入流中。
反序列化:
  1、格式化器从流中读取程序集标识和完整类型名称,如果程序集当前没有加载到AppDomain中,就加载它,如果程序集不能加载,就抛出一个SerializationException异常,对象如果不能反序列化,也抛出SerializationException异常,如果程序集已加载,格式化器将程序集标识信息和类型全名传给FormatterServices的静态方法GetTypeFromAssembly:

public static Type GetTypeFromAssembly(Assembly assem,string name);

  这个方法返回一个Type对象,它代表要反序列化的那个对象类型。
  2、格式化器调用FormatterServices的静态方法GetUnInitializedObject:

public static object GetUnInitializedObject(Type type);

  这个方法为一个新对象分配内存,但不为对象调用构造器。然而,对象的所有字段都被初始化为0或null
  3、格式化器现在要构造一个MemberInfo数组,具体做法和前面一样,都是调用FormatterServices的GetSerializableMembers方法,这个方法返回序列化好、需要反序列化的一组字段
  4、格式 化器根据流中包含的数据创建一个object数组
  5、将新分配对象、MemberInfo数组以及并行Object数组(其中包含字段值)的引用 传给FormatterServices的静态方法PopulateObjectMembers:

public static object PopulateObjectMembers(Object obj,MemberInfo []members,Object[] data);

  这个方法遍历数组,将每个字段初始化成对应的值。到此为止,对象的反序列化就算完成了。\

55、反序列化时,格式化器是用的反射机制来完成的,而反射的速度是比较慢的,如果需要避免反射,可以让需要序列化、反序列化的对象实现System.Runtime.Serialization.ISerializable

时间: 2024-11-04 15:19:51

读CLR via C#笔记的相关文章

好书一起读(85):算法笔记

这阵子看了两本算法书,<算法>和<算法导论>. 前一本读着很轻松,内容基本与大学数据结构课程重叠,示例代码用java编写,学习曲线平缓,对应用程序员来说,读它就挺好. 后一本我是边看麻省理工的<算法导论>公开课边读的,力不从心,因为我数学基础不好(详下),如果不看数学证明,其内容跟前一本就差不多了,数学基础比较好.对算法感兴趣的朋友,可以尝试之. 强烈建议各公开课平台的学习资源,质量非常高,相见恨晚!足不出户,就能学习名校的课程.但学习时别忘了跟着做习题.做笔记.预习复

读muduo网络库笔记(2.8章)

该笔记出自陈硕的<Linux多线程服务端编程>,作者比较注重版权问题,写个读后笔记应该没什么问题吧,如果有,会及时删除,没有的话继续写点笔记 -------------------------------------------- 该书在2.8章介绍了copy-on-write的一种做法实例,解决了前文提到的几个需要解决的问题. 第一版本的代码: 改版本代码问题在于,如果在Foo::doit中调用了post函数会造成死锁.(不过改bug容易定位) copy-on-write版本代码: 这是正确

C#基础及CLR基础学习笔记

一.CLR的FCL.CTS和CLS 1. CLR为Common Language Runtime,为微软.NET Framework的公共语言运行时. 2. CTS为Common Type System(通用类型系统),其是微软制定的一个正式的规范来描述类型的定义和行为,使得一种编程语言写的代码能和另一种编程语言进行沟通. CTS规定,一个类型可以包括0个或者多个成员. 字段(Field) 方法(Method) 属性(Property) 事件(Event) 同时,CTS指定类型可见性规则和类型成

编写可读代码的艺术笔记

编写可读代码的艺术 表面层次上的改进 命名.注释以及审美--可以用于代码库每一行的小提示. 简化循环和逻辑 在程序中定义循环.逻辑和变量,从而使得代码更容易理解. 重新组织你的代码 在更高层次上组织大的代码块以及在功能层次上解决问题的方法. 精选话题 把"易于理解"的思想应用于测试以及大数据结构代码的例子. 第1章:代码应当易于理解 1.代码应当易于理解. 2.代码的写法应当使别人理解它所需的时间最小化. 第一部分:表面层次的改进 第2章:把信息装到名字里 1.使用专业的单词. 2.避

[未读]CLR via C#(第4版)

内容简介 <CLR via C#(第4版)>针对CLR和.NET Framework 4.5进行深入.全面的探讨,并结合实例介绍了如何利用它们进行设计.开发和调试.全书5部分共29章.第Ⅰ部分介绍CLR基础,第Ⅱ部分解释如何设计类型,第Ⅲ部分介绍基本类型,第Ⅳ部分以核心机制为主题,第Ⅴ部分重点介绍线程处理. 通过本书的阅读,读者可以掌握CLR和.NET Framework的精髓,轻松.高效地创建高性能应用程序. 2014 Tsinghua University Press Limited Au

CLR IN C# 笔记 ——装箱和拆箱

值类型比引用类型轻,原因是他们不作为对象在托管堆中分配,不被垃圾回收,也不通过指针进行引用,但有时候需要获取对值类型的实例引用 struct Point { public int x,y; } public class Program { public void main() { ArrayList a=new ArrayList(); Point p = new Point(); p.x = p.y = 1; a.add(p); } } 查看ArrayList中的Add方法原型: public

《如何读一本书》读书笔记

1. 问自己问题 必问的4个问题 这是什么类型的书? 是小说 or 论述,是哲学论述 or 科学论述? 这本书讲了什么? 这本书是怎么论述的? 对我有什么用? 2. 回答自己的问题

读bitcoin_pdf摘录及笔记_unfinished

In this paper, we propose a solution to the double-spending problem using a peer-to-peer distributed timestamp server to generate computational proof of the chronological order of transactions.  The system is secure as long as honest nodes collective

防火墙(读鸟哥的笔记)

硬件防火墙 是由厂商设计好的主机硬件, 这部硬件防火墙内的操作系统主要以提供封包数据的过滤机制为主,并将其他不必要的功能拿掉.因为单纯作为防火墙功能而已, 因此封包过滤的效率较佳 软件防火墙 保护系统网络安全的一套软件(或称为机制),例如 Netfilter 与 TCP Wrappers 都可以称为软件防火墙. linux上防火墙依据防火墙管理的范围划分 单一主机型的管控 封包过滤型的 Netfilter 依据服务软件程序作为分析的 TCP Wrappers 网域型管控 由于此类防火墙都是当作路