天天看點

C#4.0新特性

本專題概要:

動态類型介紹

為什麼需要動态類型

動态類型的使用

動态類型背後的故事

動态類型的限制

實作動态行為

總結

引言:

  終于迎來了我們C# 4中特性了,C# 4主要有兩方面的改善——Com 互操作性的改進和動态類型的引入,然而COM互操作性這裡就不詳細介紹的,對于.Net 互操作性我将會在另外一個專題中詳細和大家分享下我所了解到的知識,本專題就和大家分享C# 4中的動态類型,對于動态類型,我剛聽到這個名詞的時候會有這些疑問的——動态類型到底是什麼的呢? 知道動态類型大概是個什麼的時候,肯定又會有這樣的疑問——C# 4中為什麼要引入動态類型的?(肯定引入之後可以完成我們之前不能做的事情了,肯定是有好處的),下面就具體介紹了動态類型有哪些内容的。

一、動态類型介紹

  提到動态類型當然就要說下靜态類型了,對于什麼是靜态類型呢? 大家都知道之前C#一直都是靜态語言(指定的是沒有引入動态類型之前,這裡說明下,不是引入了動态類型後C#就是動态語言,隻是引入動态類型後,為C#語言增添了動态語言的特性,C#仍然是靜态語言),之是以稱為靜态語言,之前我們寫代碼時,例如 int i =5;這樣的代碼,此時i 我們已經明确知道它的類型為int了,然而這樣的代碼,變量的類型的确定是在編譯時确定的,對應的,如果類型的确定是在執行時才确定的類型,這樣的類型就是動态類型(C# 4.0中新添加了一個dynamic 關鍵字來定義我們的動态類型)。面對動态類型,C#編譯器做的工作隻是完成檢查文法是否正确,但無法确定所調用的方法或屬性是否正确(之是以會這樣,主要還是因為動态類型是運作時才知道它的具體類型,是以編譯器編譯的時候肯定不知道類型,就沒辦法判斷調用的方法或屬性是不是存在和正确了,是以對于動态類型,将不能使用VS提供的智能提示的功能,這樣寫動态類型代碼時就要求開發人員對于某個動态類型必須準确知道其類型後和所具有的方法和屬性了,不能這些錯誤隻能在運作程式的過程抛出異常的方式被程式員所發現。)

補充: 講到dynamic關鍵字,也許大家會想到C# 3中的var關鍵字,這裡這裡補充說明下dynamic, var差別。var 關鍵字不過是一個指令,它告訴編譯器根據變量的初始化表達式來推斷類型。(記住var并不是類型),而C# 4中引入的dynamic是類型,但是編譯時不屬于CLR類型(指的int,string,bool,double等類型,運作時肯定CLR類型中一種的),它是包含了System.Dynamic.DynamicAttribute特性的System.Object類型,但與object又不一樣,不一樣主要展現在動态類型不會在編譯時時執行顯式轉換,下面給出一段代碼代碼大家就會很容易看出差別了:

object obj = 10;
        Console.WriteLine(obj.GetType());
        // 使用object類型此時需要強制類型轉換,不能編譯器會出現編譯錯誤
        obj = (int)obj + 10;

        dynamic dynamicnum = 10;
        Console.WriteLine(dynamicnum.GetType());
        // 對于動态類型而言,編譯時編譯器根本不知道它是什麼類型,
        // 是以編譯器就判斷不了dynamicnum的類型了,是以下面的代碼不會出現編譯時錯誤
        // 因為dynamicnum有可能是int類型,編譯器不知道該變量的具體類型不能憑空推測類型
        // 當然也就不能提示我們編譯時錯誤了
        dynamicnum = dynamicnum + 10;
           

二、為什麼需要動态類型

  第一部分和大家介紹了什麼是動态類型,對于動态類型,總結為一句話為——運作時确定的類型。然而大家了解了動态類型到底是什麼之後,當然又會出現新的問題了,即動态類型有什麼用的呢? C# 為什麼好端端的引入動态類型增加程式員的負擔呢? 事實并不是這樣的,下面就介紹了動态類型到底有什麼用,它并不是所謂給程式員帶來負擔,一定程度上講是福音

2.1 使用動态類型可以減少強制類型轉換

  從第一部分的補充也可以看到,使用動态類型不需要類型轉換是因為編譯器根本在編譯時的過程知道什麼類型,既然不知道是什麼類型,怎麼判斷該類型是否能進行什麼操作,是以也就不會出現類似“運算符“+”無法應用于“object”和“int”類型的操作數“或者”不存在int類型到某某類型的隐式轉換“的編譯時錯誤了,可能這點使用者,開發人員可能并不覺得多好的,因為動态類型沒有智能提示的功能。 但是動态類型減少了強制類型轉換的代碼之後,可讀性還是會有所增強。(這裡又涉及到個人取舍問題的, 如果自己覺得那種方式友善就用那種的,沒必要一定要用動态類型,主要是看那種方式可以讓自己和其他開發人員更好了解)

2.2 使用動态類型可以使C#靜态語言中調用Python等動态語言

  對于這點,可能朋友有個疑問,為什麼要在C#中使用Python這樣的動态語言呢? 對于這個疑問,就和在C#中通過P/Invoke與本地代碼互動,以及與COM互操作的道理一樣,假設我們要實作的功能在C#類庫中沒有,然而在Python中存在時,此時我們就可以直接調用Python中存在的功能了。

三、動态類型的使用

前面兩部分和大家介紹動态類型的一些基礎知識的,了解完基礎知識之後,大家肯定很迫不及待地想知道如何使用動态類型的,下面給出兩個例子來示範動态類型的使用的。

3.1 C# 4 通過dynamic關鍵字來實作動态類型

dynamic dyn = 5;
        Console.WriteLine(dyn.GetType());
        dyn = "test string";
        Console.WriteLine(dyn.GetType());
        dynamic startIndex = 2;
        string substring = dyn.Substring(startIndex);
        Console.WriteLine(substring);
        Console.Read();
           

運作結果為:

3.2 在C#中調用Python動态語言(要運作下面的代碼,必須下載下傳并安裝IronPython,IronPython 是在 .NET Framework 上實作的第一種動态語言。http://ironpython.codeplex.com下載下傳 )

// 引入動态類型之後
    // 可以在C#語言中與動态語言進行互動
    // 下面示範在C#中使用動态語言Python
        ScriptEngine engine = Python.CreateEngine();
        Console.Write("調用Python語言的print函數輸出: ");
        // 調用Python語言的print函數來輸出
        engine.Execute("print 'Hello world'");
        Console.Read();
           

運作結果:

四、動态類型背後的故事

知道了如何在C#中調用動态語言之後,然而為什麼C# 為什麼可以使用動态類型呢?C#編譯器到底在背後為我們動态類型做了些什麼事情的呢? 對于這些問題,答案就是DLR(Dynamic Language Runtime,動态語言運作時),DLR使得C#中可以調用動态語言以及使用dynamic的動态類型。提到DLR時,可能大家會想到.Net Framework中的CLR(公共語言運作時),然而DLR 與CLR到底是什麼關系呢?下面就看看.Net 4中的元件結構圖,相信大家看完之後就會明白兩者之間的差別:

從圖中可以看出,DLR是建立在CLR的基礎之上的,其實動态語言運作時是動态語言和C#編譯器用來動态執行代碼的庫,它不具有JIT編譯,垃圾回收等功能。然而DLR在代碼的執行過程中扮演的是什麼樣的角色呢? DLR所扮演的角色就是——DLR通過它的綁定器(binder)和調用點(callsite),元對象來把代碼轉換為表達式樹,然後再把表達式樹編譯為IL代碼,最後由CLR編譯為本地代碼(DLR就是幫助C#編譯器來識别動态類型)。 這裡DLR扮演的角色并不是憑空想象出來的,而且檢視它的反編譯代碼來推出來的,下面就具體給出一個例子來說明DLR背後所做的事情。C#源代碼如下:

class Program

{

static void Main(string[] args)

{

dynamic text = “test text”;

int startIndex = 2;

string substring = text.Substring(startIndex);

Console.Read();

}

}

通過Reflector工具檢視生成的IL代碼如下:

private static void Main(string[] args)

{

object text = “test text”;

int startIndex = 2;

if (o__SiteContainer0.<>p__Site1 == null)

{

// 建立用于将dynamic類型隐式轉換為字元串的調用點

o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, string>>.Create(Binder.Convert(CSharpBinderFlags.None, typeof(string), typeof(Program)));

}

if (o__SiteContainer0.<>p__Site2 == null)

{

// 建立用于調用Substring函數的調用點

o__SiteContainer0.<>p__Site2 = CallSite<Func<CallSite, object, int, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.None, “Substring”, null, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) }));

}

// 調用調用點,首先調用<>p_Site2,即Substring方法,再調用<>P_Site1來将結果進行轉換
string substring = <Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, <Main>o__SiteContainer0.<>p__Site2.Target(<Main>o__SiteContainer0.<>p__Site2, text, startIndex));
Console.Read();
           

}

//編譯器生成的内嵌類型為

[CompilerGenerated]

private static class o__SiteContainer0

{

// Fields

public static CallSite<Func<CallSite, object, string>> <>p__Site1;

public static CallSite<Func<CallSite, object, int, object>> <>p__Site2;

}

從IL代碼中可以看出Main方法内包含兩個動态操作,因為編譯器生成的内嵌類型包含兩個調用點(CallSite,CallSite即是System.Runtime.CompilerServices命名空間下的一個類,關于CallSite的具體資訊可以檢視MSDN中的介紹——CallSite )字段,一個是調用Substring方法(即<>p__Site2),一個是将結果(編譯時時dynamic)動态地轉換為字元串(即<>p__Site1),下面給出動态類型的執行過程(注意DLR中有一個緩存的概念):

五、動态類型的限制

相信通過前面幾部分的介紹大家已經對動态類型有了一定的了解的,尤其是第四部分的介紹之後,大家應該對于動态類型的執行過程也有了一個清晰的認識了,然而有些函數時不能通過動态綁定來進行調用的,這裡就涉及到類型類型的限制:

5.1 不能用動态類型作為參數調用擴充方法

不能用動态類型作為參數來調用擴充方法的原因是——調用點知道編譯器所知道的靜态類型,但是它不知道調用所在的源檔案在哪裡,以及using指令引入了哪些命名空間,是以在編譯時調用點就找不到哪些擴充方法可以使用,是以就會出現編譯時錯誤。下面給出一個簡單的示例程式:

var numbers = Enumerable.Range(10, 10);

dynamic number = 4;

var error = numbers.Take(number); // 編譯時錯誤

// 通過下面的方式來解決這個問題
        // 1. 将動态類型轉換為正确的類型
        var right1 = numbers.Take((int)number);
        // 2. 用調用靜态方法的方式來進行調用
        var right2 = Enumerable.Take(numbers, number);
           

5.2 委托與動态類型不能隐式轉換的限制

如果需要将Lambda表達式,匿名方法轉化為動态類型時,此時編譯器必須知道委托的确切類型,不能不加強制轉化就把他們設定為Delegae或object變量,此時不同string,int類型(因為前面int,string類型可以隐式轉化為動态類型,編譯器此時會把他們設定為object類型。但是匿名方法和Lambda表達式不能隐式轉化為動态類型),如果需要完成這樣的轉換,此時必須強制指定委托的類型,下面是一個示範例子:

dynamic lambdarestrict = x => x + 1; // 編譯時錯誤

// 解決方案

dynamic rightlambda =(Func<int,int>)( x=>x+1);

dynamic methodrestrict = Console.WriteLine; // 編譯時錯誤
        // 解決方案
        dynamic rightmethod =(Action<string>)Console.WriteLine;
           

5.3 動态類型不能調用構造函數和靜态方法的限制——即不能對動态類型調用構造函數或靜态方法,因為此時編譯器無法指定具體的類型。

dynamic s = new dynamic();

5.4 類型聲明和泛型類型參數

不能聲明一個基類為dynamic的類型,也不能将dynamic用于類型參數的限制,或作為類型所實作的接口的一部分,下面看一些具體的例子來加深概念的了解:

// 基類不能為dynamic 類型

class DynamicBaseType : dynamic

{

}

// dynamic類型不能為類型參數的限制

class DynamicTypeConstrain where T : dynamic

{

}

// 不能作為所實作接口的一部分

class DynamicInterface : IEnumerable

{

}

六、實作動态的行為

介紹了這麼動态類型,是不是大家都迫不及待地想知道如果讓自己的類型具有動态的行為呢? 然而實作動态行為有三種方式:

使用ExpandObject

使用DynamicObject

實作IDynamicMetaObjectProvider接口.

下面就從最簡單的方式:

6.1 使用ExpandObject來實作動态的行為

View Code

using System;

// 引入額外的命名空間

using System.Dynamic;

namespace 自定義動态類型

{

class Program

{

static void Main(string[] args)

{

dynamic expand = new ExpandoObject();

// 動态為expand類型綁定屬性

expand.Name = “Learning Hard”;

expand.Age = 24;

// 動态為expand類型綁定方法
        expand.Addmethod = (Func<int, int>)(x => x + 1);
        // 通路expand類型的屬性和方法
        Console.WriteLine("expand類型的姓名為:"+expand.Name+" 年齡為: "+expand.Age);
        Console.WriteLine("調用expand類型的動态綁定的方法:" +expand.Addmethod(5));
        Console.Read();
    }
}
           

}

運作的結果和預期的一樣,運作結果為:

6.2 使用DynamicObject來實作動态行為

View Code

static void Main(string[] args)

{

dynamic dynamicobj = new DynamicType();

dynamicobj.CallMethod();

dynamicobj.Name = “Learning Hard”;

dynamicobj.Age = “24”;

Console.Read();

}

class DynamicType : DynamicObject

{

// 重寫方法,

// TryXXX方法表示對對象的動态調用

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)

{

Console.WriteLine(binder.Name +" 方法正在被調用");

result = null;

return true;

}

public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        Console.WriteLine(binder.Name + " 屬性被設定," + "設定的值為: " + value);
        return true;
    }
}
           

運作結果為:

6.3 實作IDynamicMetaObjectProvider接口來實作動态行為

由于Dynamic類型在運作時來動态建立對象的,是以對該類型的每個成員的通路都會調用GetMetaObject方法來獲得動态對象,然後通過這個動态對象來進行調用,是以實作IDynamicMetaObjectProvider接口,需要實作一個GetMetaObject方法來傳回DynamicMetaObject對象,示範代碼如下:

static void Main(string[] args)

{

dynamic dynamicobj2 = new DynamicType2();

dynamicobj2.Call();

Console.Read();

}

public class DynamicType2 : IDynamicMetaObjectProvider

{

public DynamicMetaObject GetMetaObject(Expression parameter)

{

Console.WriteLine(“開始獲得中繼資料…”);

return new Metadynamic(parameter,this);

}

}

// 自定義Metadynamic類
public class Metadynamic : DynamicMetaObject
{
    internal Metadynamic(Expression expression, DynamicType2 value)
        : base(expression, BindingRestrictions.Empty, value)
    {
    }
    // 重寫響應成員調用方法
    public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
    {
        // 獲得真正的對象
        DynamicType2 target = (DynamicType2)base.Value;
        Expression self = Expression.Convert(base.Expression, typeof(DynamicType2));
        var restrictions = BindingRestrictions.GetInstanceRestriction(self, target);
        // 輸出綁定方法名
        Console.WriteLine(binder.Name + " 方法被調用了");
        return new DynamicMetaObject(self, restrictions);
    }
}
           

運作結果為: