程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 讀CLR via C#筆記,

讀CLR via C#筆記,

編輯:C#入門知識

讀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\Projects\
Sample\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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved