程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#3.0新特性(三)-詳說自動屬性和匿名類型

C#3.0新特性(三)-詳說自動屬性和匿名類型

編輯:關於C#

寫在前邊:

不知道怎麼的,上一篇說放到首頁看看,今天來看才發現沒有放上去,做事情還真不細心,改、改、改!其實,早知道C# 4.0都有了,但是連C# 3.0都還沒有弄清楚,使用還是堅持這個系列,做完再寫C# 4.0吧。技術前輩們肯定早都知道這些東西了,不過呢,我也只是學習,只是把我的學習同大家分享而已!

一、自動實現的屬性

全稱應該叫自動實現的屬性(Auto-implemented properties),在上一篇中,給了簡單的例子,說的是當屬性訪問器中不需要其他邏輯時,自動實現的屬性可使屬性聲明變得更加簡潔。如前邊的例子,在C# 2.0中:

private int m_one;
public int One
{
  get { return m_one; }
  set { m_one = value; }
}

這個屬性只有存(set)取(get)邏輯,沒有其它諸如動態分配、或是按條件存取的邏輯,在C# 3.0中,完全可以寫成:

public int One { get; set; }

從而不需要創建與該屬性對應的私有變量。

自動實現的屬性必須同時聲明get和set訪問器。若要創建readonly自動實現屬性,請給予它private set訪問器。如:

public string Name { get; private set; }

這樣,這個屬性只讀,不能對其進行存操作。那有人肯定要問,要給Name賦值,怎麼辦。仔細理解這個private set的含義,在面向對象的概念中,private表示在自己類的內部還是可以訪問的,也就是,這裡說的只讀屬性,在定義這個屬性的類本身中,還是可以訪問的,如下:

public class EDClass
{
  public string Name { get; private set; }
  public EDClass()
  {
    Name = "qq";
  }
  public void setName()
  {
    Name = "qq";
  }
}

這樣是完全沒有問題的,但是,如果通過obj.Name = value肯定不行的,要不然就不叫readonly了。例如:

EDClass cls = new EDClass();
cls.Name = "qq";

報錯:The property or indexer 'CSharpStudy.EDClass.Name' cannot be used in this context because the set accessor is inaccessible

還有一點需要說明,自動實現的屬性(Property) 不允許具有屬性 (Attribute)。如果您必須在屬性(Property) 的後備字段上使用屬性(Attribute),則應該只創建常規屬性(Property)。好繞口,仔細分析這些概念,在面向對象的概念中,類的成員(member)又稱為屬性(Attribute),其實,在一般來講,在C++中稱為成員(Member)變量,在C#中稱為屬性(Attribute),或字段。而這裡的自動屬性用的是Property,試想,定義了一個自動屬性:

public string Name { get; set; }

又想定義一個私有成員(屬性,Attribute)與之關聯:

private string _name;

不會報錯,但是肯定關聯不了,它只被認為是類的一個私有成員變量。

二、匿名類型

在第一篇中,給了簡單的匿名類型的例子,這裡再詳細說。

匿名類型提供了一種方便的方法,可用來將一組只讀屬性封裝到單個對象中,而無需首先顯式定義一個類型。

類型名由編譯器生成,並且不能在源代碼級使用。

這些屬性的類型由編譯器推斷。如:

匿名類型通常用在查詢表達式的select子句中,以便返回源序列中每個對象的屬性子集。

匿名類型是使用new 運算符和對象初始值設定項(初始化器)創建的。

匿名類型是由一個或多個公共只讀屬性組成的類類型。不允許包含其他種類的類成員(如方法或事件)。如:

var noname = new { name = "yyao", age = 24 };//匿名類型

也就是說,匿名類型由編譯器來推斷出一個嚴格的類型,編譯器是可以對其類型進行判定的,只是其類型的名字不由編程者所知而已。如,有如下定義:

var noname = new { namea = "yyao", age = 24 };//匿名類型
var qname = new { namea = "selfcherish", age = 25 };
var gname = new { username = "selfcherish", age = 25 };
qname = noname;
gname = qname;

這三個定義,第一個賦值沒有任何問題,說明編譯器自動判斷了它們兩者的類型是相同的,從而可以賦值。而第二個賦值,則報錯:Cannot implicitly convert type 'AnonymousType#1' to 'AnonymousType#2'。定義匿名類型的時候,還需要注意,不能用null賦初值。如:

var vara = new { hei = null, id = 5 };

將報錯:Cannot assign <null> to anonymous type property。另外,匿名類型是由一個或多個只讀屬性組成的類類型,如,gname可以通過gname.username和gname.age來訪問,但是想通過:

gname.age = 26;

則報錯:Property or indexer 'AnonymousType#1.age' cannot be assigned to -- it is read only。

匿名類型,最常見的使用方式是用其他類型的一些屬性初始化匿名類型:

MyClass[] ojbset = new MyClass[12];
var vaobj =
  from obj in ojbset
  select new { obj.Name, obj.Number };
foreach (var v in vaobj)
{
  Console.WriteLine("Name={0}, Number={1}", v.Name, v.Number);
}

這裡的MyClass可能不只兩個屬性,只選了Name和Number這兩個屬性來初始化vaobj。另外,在將匿名類型分配給變量時,必須使用var構造初始化該變量,因為匿名類型不能用某類型來定義,只能用var,也就是說,只有編譯器能夠訪問匿名類型的基礎名稱,編譯器通過var來訪問匿名類型的基礎變量vaobj。

三、匿名類型的解析:

匿名類型的基礎是對象初始化器,匿名類型從對象初始化器(object initializer)自動推斷和生成的元組類型。下面我們來看看匿名類型到底怎麼生成的和我們原來的定義方式有什麼區別:

var noname = new { namea = "dd", age = 24 };//匿名類型
var qname = new { namea = "ff", age = 25 };

第一句,給noname賦了一個匿名類型,在編譯時,編譯器使用對象初始化器推斷的屬性來創建見一個新的匿名類型,該類型擁有aname啊和age的屬性,在運行時,會創建新類型的一個實例同時namea和age屬性將會被設置為對象初始化器中指定的值“dd”、24;和上面幾節裡描述的一樣這裡大家一定會想到,肯定又是在編譯器裡封裝了一些處理;確實是這樣,下面這段代碼描述編譯器針對匿名類型語句具體做了哪些工作:

class __Anonymous1
{
  private string name;
  private int age;
  public string Name { get { return name; } set { name = value; } }
  public int Age { get { return age; } set { age = value; } }
}
__Anonymous1 noname = new __Anonymous1();
noname.Name="dd";
noname.Age=24;

這段代碼就是我們非常熟悉的寫法,編譯器就是在後台依據匿名類型解析類型,創建新類,初始化對象;如果你創建了多個相似的匿名類型,C#編譯器會聰明的發現這一點,只生成一個類和它的多個實例;

上邊說到,這兩句中,noname和qname的類型相同,可以通過Visual Studio 2008的工具ILDasm來驗證。這個工具能對一個程序或者它的類庫執行反匯編處理,顯示由C#編譯器生成的CIL代碼,通過反匯編可以列出封裝在程序集中的類型信息。為此,先給出完整的類代碼:

public class EDClass
{
  public string Name { get; private set; }
  public EDClass()
  {
    Name = "qq1";
  }
  public void setName()
  {
    Name = "qq";
    var noname = new { namea = "dd", age = 24 };//匿名類型
    var qname = new { namea = "ff", age = 25 };
    //var vara = new { hei = null, id = 5 };
    if (qname == noname)
    {
      //!=,所以不能輸出
      Console.WriteLine("qname == noname");
    }
    if (qname.Equals(noname))
    {
      //Equal,能夠輸出
      Console.WriteLine("qname equal noname");
    }
    if (qname.GetHashCode() == noname.GetHashCode())
    {
      //能夠輸出
      Console.WriteLine("same hashcode");
    }
  }
}

當然,需要添加運行該代碼的主函數。這裡省略。

在Visual Studio 2008的Tools菜單下,選擇ILDasm,即可打開這個工具:然後在這個工具的File菜單下點Open,選擇剛剛運行的程序,並打開。如圖:

然後,先點擊View菜單下的Show source lines菜單項,目的是,反匯編後,可以在看CIL代碼的同時,查看相應的源代碼。然後,雙擊上圖中的setName方法,得到其反匯編後的詳細信息,雙擊後,關於noname和qname,可以得到如下代碼段:

//000108:       var noname = new { namea = "dd", age = 24 };//????????
 IL_000d: ldstr   "dd"
 IL_0012: ldc.i4.s  24
 IL_0014: newobj   instance void class '<>f__AnonymousType0`2'<string,int32>::.ctor(!0,
                                            !1)
 IL_0019: stloc.0
//000109:       var qname = new { namea = "ff", age = 25 };
 IL_001a: ldstr   "ff"
 IL_001f: ldc.i4.s  25
 IL_0021: newobj   instance void class '<>f__AnonymousType0`2'<string,int32>::.ctor(!0,
                                            !1)
 IL_0026: stloc.1

可以看出,noname和qname都是類類型<>f__AnonymousType0`2,所以,它們可以相互賦值。再看,上圖中,最下方恰好有一項<>f__AnonymousType0`2,點擊前邊的加號,就可以看到其詳細信息。如圖:

另外,使用ILDasm這個工具,也可以查看自動屬性的信息,如這個類中的自動屬性Name,在第一個圖中,有這樣一項:<Name>k_BackingField:private string,也即是說,編譯器通過'<Name>k__BackingField'的方式對Name(和與之相關的私有變量)進行了聲明。

三、匿名類型的總結

匿名類型是直接從對象派生的引用類型。盡管應用程序無法訪問匿名類型,但編譯器仍會為其提供一個名稱。

如果兩個或更多個匿名類型以相同的順序具有相同數量和種類的屬性,則編譯器會將這些匿名類型視為相同的類型,並且它們共享編譯器生成的相同類型信息。如上邊賦值的那個例子。

匿名類型具有方法范圍。也就是說,聲明或是定義的匿名類型,只能在聲明或定義它的那個方法中使用,實際上,想定義匿名成員類型,根本是做不到的,如:private var ame;private var hehe = new { namea = "yyao", age = 24 };想通過這樣的方式,將他們定義為某類的成員,則報相同的錯:The contextual keyword 'var' may only appear within a local variable declaration。

匿名類型不能像屬性一樣包含不安全類型。

由於匿名類型上的Equals 和 GetHashCode 方法是根據屬性的Equals 和GetHashcode 定義的,因此僅當同一匿名類型的兩個實例的所有屬性都相等時,這兩個實例才相等。如:

var noname = new { namea = "yyao", age = 24 };//匿名類型
var qname = new { namea = "yyao", age = 24 };
if (qname == noname)
{
  //!=,所以不能輸出
  Console.WriteLine("qname == noname");
}
if (qname.Equals(noname))
{
  //Equal,能夠輸出
  Console.WriteLine("qname equal noname");
}
if (qname.GetHashCode() == noname.GetHashCode())
{
  //能夠輸出
  Console.WriteLine("same hashcode");
}

四、總結

今天的很多東西,在第一篇和第二篇都已經用到過,今天再詳細的講講其原理和內部機制,加深印象,只是,學習中,過得太少了,以後,時時來看看自己的隨筆吧,也不失為復習的好方法。

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