C#中的動態特性


眾所周知,C#和Java一樣,都是一門靜態語言。在C# 4.0之前,想要和動態語言(諸如Python、Javascript等)進行方便地互操作是一件不太容易的事情。而C# 4.0為我們帶來的dynamic關鍵字,使得我們可以方便的和動態語言進行互操作。本文將從如下幾個方便來闡述:

  1. 1.dynamic的使用
  2. 2.dynamic原理(DLR)
  3. 3.動態行為實現
  4. 4.實例剖析:Javascript DLR Engine

1.dynamic的使用

關於dynamic的使用,這里主要講兩個例子:

例子1:

static void Main(string[] args)
{
	int i = 2;
	dynamic j = i;
	Console.WriteLine(j.GetType());//System.Int32
	int s = j + "3";//occur runtime exception
	Console.WriteLine(s);
	Console.ReadKey();
}

正常來說,int s = ? + "3";這句編譯是通不過的,因為一個string類型無法隱式的轉換成int類型。然而當?為dynamic類型時,這段代碼是可以編譯通過的,但在運行時會報無法將類型“string”隱式轉換為“int”的異常。這看起來似乎是將編譯時的錯誤推遲到了運行時。

例子2:

static void Main(string[] args)
{
	var d = new {i = 1, j = 2};
	Console.WriteLine(Calculate(d));//3
	Console.ReadKey();
}

static dynamic Calculate(dynamic d)
{
	return d.i + d.j;
}

首先聲明了一個匿名類型對象,然后將該對象作為參數傳給Calculate方法。Calculate方法接受一個dynamic類型的參數,作加操作。這樣達到了操作一個隱式類型的效果。

2. dynamic原理(DLR)

在上面的例子中,我們簡單感受了下dynamic關鍵字的強大。一個強大事物的背后總會有各種支撐其發展的事物,dynamic也不例外,[DLR](http://dlr.codeplex.com/)(Dyanmic Language Runtime)庫就是其支撐。

DLR框架

DLR框架

以上是DLR的框架結構圖。下面我們將通過一個簡單的例子,來闡述dynamic的原理:

static void Main(string[] args)
    {
        // Source 
        var student = new Student { ID = 1, Name = "jello" };

        // Dynamic Assign
        dynamic d = student;
        Console.WriteLine(d.ID);
		
        Console.ReadKey();
    }
}

class Student
{
    public int ID { get; set; }
    public string Name { get; set; }
}

通過反編譯,代碼如下:

private static void Main(string[] args)
	{
		Student student = new Student
		{
			ID = 1,
			Name = "jello"
		};
		object d = student;
		if (Program.<Main>o__SiteContainer1.<>p__Site2 == null)
		{
			Program.<Main>o__SiteContainer1.<>p__Site2 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "WriteLine", null, typeof(Program), new CSharpArgumentInfo[]
			{
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null),
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
			}));
		}
		Action<CallSite, Type, object> arg_D1_0 = Program.<Main>o__SiteContainer1.<>p__Site2.Target;
		CallSite arg_D1_1 = Program.<Main>o__SiteContainer1.<>p__Site2;
		Type arg_D1_2 = typeof(Console);
		if (Program.<Main>o__SiteContainer1.<>p__Site3 == null)
		{
			Program.<Main>o__SiteContainer1.<>p__Site3 = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, "ID", typeof(Program), new CSharpArgumentInfo[]
			{
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
			}));
		}
		arg_D1_0(arg_D1_1, arg_D1_2, Program.<Main>o__SiteContainer1.<>p__Site3.Target(Program.<Main>o__SiteContainer1.<>p__Site3, d));
		if (Program.<Main>o__SiteContainer1.<>p__Site4 == null)
		{
			Program.<Main>o__SiteContainer1.<>p__Site4 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "WriteLine", null, typeof(Program), new CSharpArgumentInfo[]
			{
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null),
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
			}));
		}
		Action<CallSite, Type, object> arg_189_0 = Program.<Main>o__SiteContainer1.<>p__Site4.Target;
		CallSite arg_189_1 = Program.<Main>o__SiteContainer1.<>p__Site4;
		Type arg_189_2 = typeof(Console);
		if (Program.<Main>o__SiteContainer1.<>p__Site5 == null)
		{
			Program.<Main>o__SiteContainer1.<>p__Site5 = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, "Name", typeof(Program), new CSharpArgumentInfo[]
			{
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
			}));
		}
		arg_189_0(arg_189_1, arg_189_2, Program.<Main>o__SiteContainer1.<>p__Site5.Target(Program.<Main>o__SiteContainer1.<>p__Site5, d));
		Console.ReadKey();
	}

	[CompilerGenerated]
	private static class <Main>o__SiteContainer1
	{
		public static CallSite<Action<CallSite, Type, object>> <>p__Site2;

		public static CallSite<Func<CallSite, object, object>> <>p__Site3;

		public static CallSite<Action<CallSite, Type, object>> <>p__Site4;

		public static CallSite<Func<CallSite, object, object>> <>p__Site5;
	}

我們看到,編譯器會為我們生成一個

o__SiteContainer1類,里面包含四個CallSite

  1. <>p__Site2:對應於第一個Console.WriteLine(dynamic)
  2. <>p__Site3:對應於dynamic.ID
  3. <>p__Site4:對應於第二個Console.WriteLine(dynamic)
  4. <>p__Site5:對應於dynamic.Name

大概的步驟如下:

  1. 將dynamic聲明的變量變為object類型
  2. 解析表達式並執行,通過Binder構造CallSite,內部通過構造Expression Tree實現。Expression Tree可編譯成IL,然后交由CLR編譯成Native Code

DLR采用三級緩存,包括L0、L1和L2。緩存以不同的方式將信息存儲在不同的作用域中。每個調用點包含自己的L0和L1緩存。而L2緩存可以被多個類似的調用點共享。拿上面例子為例:

  1. 首次構造<>p__Site2時,會通過CallSite<Action<CallSite, Type, object>>.Create(CallSiteBinder)構造CallSite的同時,在L0緩存基於Site History的專用委托,可通過CallSite.Target獲取。
  2. 當調用CallSite.Target委托時,會去調用UpdateDelegates的UpdateAndExecute×××(CallSite)方法,通過該方法去更新緩存並執行委托。
  3. L1緩存緩存了dynamic site的歷史記錄(規則),是一個委托數組,L2緩存緩存了由同一個binder產生的所有規則,是一個字典,Key為委托,Value為RuleCache ,T為委托類型。

可以做如下實驗:

Console.WriteLine("-------Test--------");
        Console.WriteLine("callsite1-Target:" + action.GetHashCode());
        Console.WriteLine("callsite3-Target:" + action1.GetHashCode());
        var rules1 = CallSiteContainer.CallSite1.GetType()
            .GetField("Rules", BindingFlags.Instance | BindingFlags.NonPublic)
            .GetValue(CallSiteContainer.CallSite1) as Action<CallSite, Type, object>[];
        var rules2 = CallSiteContainer.CallSite3.GetType()
            .GetField("Rules", BindingFlags.Instance | BindingFlags.NonPublic)
            .GetValue(CallSiteContainer.CallSite3) as Action<CallSite, Type, object>[];
        if(rules1 != null && rules1.Length > 0)
            Console.WriteLine("callsite1-Rules:" + rules1[0].GetHashCode());
        if (rules2 != null && rules2.Length > 0)
            Console.WriteLine("callsite3-Rules:" + rules2[0].GetHashCode());
        var binderCache1 =
            CallSiteContainer.CallSite1.Binder.GetType()
                .GetField("Cache", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(CallSiteContainer.CallSite1.Binder) as Dictionary<Type, object>;
        var binderCache2 =
            CallSiteContainer.CallSite3.Binder.GetType()
                .GetField("Cache", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(CallSiteContainer.CallSite3.Binder) as Dictionary<Type, object>;
        var binderCache3 =
            CallSiteContainer.CallSite4.Binder.GetType()
                .GetField("Cache", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(CallSiteContainer.CallSite4.Binder) as Dictionary<Type, object>;
        var binderCache4 =
            CallSiteContainer.CallSite5.Binder.GetType()
                .GetField("Cache", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(CallSiteContainer.CallSite5.Binder) as Dictionary<Type, object>; 
        if (binderCache1 != null)
        {
            Console.WriteLine("callsite1-Binder-Cache:");
            foreach (var o2 in binderCache1)
            {
                Console.WriteLine("Key-Type:{0},Key-Value:{1},Value-Type:{2},Value-Value:{3}",o2.Key.Name,o2.Key.GetHashCode(),o2.Value.GetType().Name,o2.Value.GetHashCode());
            }
        }
        if (binderCache2 != null)
        {
            Console.WriteLine("callsite3-Binder-Cache:");
            foreach (var o2 in binderCache2)
            {
                Console.WriteLine("Key-Type:{0},Key-Value:{1},Value-Type:{2},Value-Value:{3}", o2.Key.Name, o2.Key.GetHashCode(), o2.Value.GetType().Name, o2.Value.GetHashCode());
            }
        }
        if (binderCache3 != null)
        {
            Console.WriteLine("callsite4-Binder-Cache:");
            foreach (var o2 in binderCache3)
            {
                Console.WriteLine("Key-Type:{0},Key-Value:{1},Value-Type:{2},Value-Value:{3}", o2.Key.Name, o2.Key.GetHashCode(), o2.Value.GetType().Name, o2.Value.GetHashCode());
            }
        }
        if (binderCache4 != null)
        {
            Console.WriteLine("callsite5-Binder-Cache:");
            foreach (var o2 in binderCache4)
            {
                Console.WriteLine("Key-Type:{0},Key-Value:{1},Value-Type:{2},Value-Value:{3}", o2.Key.Name, o2.Key.GetHashCode(), o2.Value.GetType().Name, o2.Value.GetHashCode());
            }
        }

測試結果如下:

測試結果

3.動態行為實現

C#中要想實現動態行為,需要實現IDynamicMetaObjectProvider接口,在DLR中也提供了兩個默認的實現:ExpandoObjectDynamicObject

3.1ExpandoObject

ExpandoObject類可以在運行時動態地操作(包括添加、刪除、賦值和取值等)成員,由於實現了IDynamicMetaObjectProvider接口,使得它可以在支持DLR互操作性模型的各種語言之間共享ExpandoObject類的實例。

class Program
{
    static void Main(string[] args)
    {
        // Use dynamic keyword to enable late binding for an instance of the ExpandoObject Class
        dynamic sampleObject = new ExpandoObject();

        // Add number field for sampleObject
        sampleObject.number = 10;
        Console.WriteLine(sampleObject.number);

        // Add Increase method for sampleObject
        sampleObject.Increase = (Action) (() => { sampleObject.number++; });
        sampleObject.Increase();
        Console.WriteLine(sampleObject.number);

        // Create a new event and initialize it with null.
        sampleObject.sampleEvent = null;

        // Add an event handler.
        sampleObject.sampleEvent += new EventHandler(SampleHandler);

        // Raise an event for testing purposes.
        sampleObject.sampleEvent(sampleObject, new EventArgs());

        // Attach PropertyChanged Event
        ((INotifyPropertyChanged)sampleObject).PropertyChanged += Program_PropertyChanged;
        sampleObject.number = 6;

        // Delete Increase method for sampleObject
        Console.WriteLine("Delete Increase method:" +
                          ((IDictionary<string, object>) sampleObject).Remove("Increase"));
        //sampleObject.Increase();// Throw a exception of which sampleObject don't contain Increase Method

        Console.ReadKey();
    }

    static void Program_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        Console.WriteLine("{0} has changed", e.PropertyName);
    }

    private static void SampleHandler(object sender, EventArgs e)
    {
        Console.WriteLine("SampleHandler for {0} event", sender);
    }
}

運行結果如下:
Result

3.2DynamicObject

DynamicObject類與DLR的交互比ExpandoObject類更加細粒度,它能夠定義在動態對象上哪些操作可以執行以及如何執行。由於它的構造函數是Protected的,所以無法直接new,需要繼承該類。

// The class derived from DynamicObject.
    public class DynamicDictionary : DynamicObject
    {
        // The inner dictionary.
        Dictionary<string, object> dictionary
            = new Dictionary<string, object>();

        // This property returns the number of elements
        // in the inner dictionary.
        public int Count
        {
            get
            {
                return dictionary.Count;
            }
        }

        // If you try to get a value of a property 
        // not defined in the class, this method is called.
        public override bool TryGetMember(
            GetMemberBinder binder, out object result)
        {
            // Converting the property name to lowercase
            // so that property names become case-insensitive.
            string name = binder.Name.ToLower();

            // If the property name is found in a dictionary,
            // set the result parameter to the property value and return true.
            // Otherwise, return false.
            return dictionary.TryGetValue(name, out result);
        }

        // If you try to set a value of a property that is
        // not defined in the class, this method is called.
        public override bool TrySetMember(
            SetMemberBinder binder, object value)
        {
            // Converting the property name to lowercase
            // so that property names become case-insensitive.
            dictionary[binder.Name.ToLower()] = value;

            // You can always add a value to a dictionary,
            // so this method always returns true.
            return true;
        }
    }
	
	static void Main(string[] args)
	{
		// Creating a dynamic dictionary.
        dynamic person = new DynamicDictionary();

        // Adding new dynamic properties. 
        // The TrySetMember method is called.
        person.FirstName = "Ellen";
        person.LastName = "Adams";
        // Getting values of the dynamic properties.
        // The TryGetMember method is called.
        // Note that property names are case-insensitive.
        Console.WriteLine(person.firstname + " " + person.lastname);

        // Getting the value of the Count property.
        // The TryGetMember is not called, 
        // because the property is defined in the class.
        Console.WriteLine(
            "Number of dynamic properties:" + person.Count);

        // The following statement throws an exception at run time.
        // There is no "address" property,
        // so the TryGetMember method returns false and this causes a
        // RuntimeBinderException.
        // Console.WriteLine(person.address);
	}

運行結果如下:

DynamicObject Sample Result

3.3IDynamicMetaObjectProvider

如果你只是在運行時簡單地做一些動態的操作,可以使用ExpandoObject類;如果你想稍微深入一些,在動態對象上定義哪些操作可以執行以及如何執行,可以使用DynamicObject;如果你想更完全的控制動態對象的行為的話,你可以實現IDynamicMetaObjectProvider接口。使用IDynamicMetaObjectProvider接口是在一個比DynamicObject類更低級地對DLR庫的依賴。DLR使用可擴展的表達式樹來實現動態行為。

public class DynamicDictionary : IDynamicMetaObjectProvider
    {

        #region IDynamicMetaObjectProvider Members

        public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
        {
            return new DynamicDictionaryMetaObject(parameter, this);
        }

        #endregion


        private class DynamicDictionaryMetaObject : DynamicMetaObject
        {
            public DynamicDictionaryMetaObject(Expression expression, object value)
                : base(expression, BindingRestrictions.Empty, value)
            {

            }

            public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
            {
                // Method to call in the containing class
                string methodName = "SetDictionaryEntry";

                // Setup the binding restrictions
                BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);

                // Setup the parameters
                Expression[] args = new Expression[2];
                // First parameter is the name of the property to set
                args[0] = Expression.Constant(binder.Name);
                // Second parameter is the value
                args[1] = Expression.Convert(value.Expression, typeof(object));

                // Setup the 'this' reference
                Expression self = Expression.Convert(Expression, LimitType);

                // Setup the method call expression
                Expression methodCall = Expression.Call(self, typeof(DynamicDictionary).GetMethod(methodName), args);

                // Create a meta objecte to invoke set later
                DynamicMetaObject setDictionaryEntry = new DynamicMetaObject(methodCall, restrictions);
                return setDictionaryEntry;
            }

            public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
            {
                // Method call in the containing class
                string methodName = "GetDictionaryEntry";

                // One parameter
                Expression[] parameters = new Expression[]
            {
                Expression.Constant(binder.Name)
            };

                // Setup the 'this' reference
                Expression self = Expression.Convert(Expression, LimitType);

                // Setup the method call expression
                Expression methodCall = Expression.Call(self,
                    typeof(DynamicDictionary).GetMethod(methodName), parameters);

                // Setup the binding restrictions
                BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);

                DynamicMetaObject getDictionaryEntry = new DynamicMetaObject(methodCall, restrictions);

                return getDictionaryEntry;
            }

            public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
            {
                StringBuilder paramInfo = new StringBuilder();
                paramInfo.AppendFormat("Calling {0}(", binder.Name);
                foreach (var item in args)
                {
                    paramInfo.AppendFormat("{0}, ", item.Value);
                }
                paramInfo.Append(")");

                Expression[] parameters = new Expression[]
                {
                    Expression.Constant(paramInfo.ToString())
                };

                Expression self = Expression.Convert(Expression, LimitType);

                Expression methodCall = Expression.Call(self, typeof(DynamicDictionary).GetMethod("WriteMethodInfo"),
                    parameters);

                BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);

                return new DynamicMetaObject(methodCall, restrictions);
            }
        }

        private Dictionary<string, object> storage = new Dictionary<string, object>();

        public object SetDictionaryEntry(string key, object value)
        {
            if (storage.ContainsKey(key))
            {
                storage[key] = value;
            }
            else
            {
                storage.Add(key, value);
            }
            return value;
        }

        public object GetDictionaryEntry(string key)
        {
            object result = null;
            if (storage.ContainsKey(key))
            {
                result = storage[key];
            }
            return result;
        }

        public object WriteMethodInfo(string methodInfo)
        {
            Console.WriteLine(methodInfo);
            return 42;// because it is the answer to everything
        }

        public override string ToString()
        {
            StringWriter writer = new StringWriter();
            foreach (var o in storage)
            {
                writer.WriteLine("{0}:\t{1}", o.Key, o.Value);
            }
            return writer.ToString();
        }
    }

調用如下:

static void Main(string[] args)
    {
        dynamic dynamicDictionary = new DynamicDictionary();
        dynamicDictionary.FirstName = "jello";
        dynamicDictionary.LastName = "chen";
        dynamicDictionary.Say();
        Console.WriteLine(dynamicDictionary.FirstName);
        Console.ReadKey();
    }

結果如圖所示:

調用結果

4.實例剖析:Javascript DLR Engine

Javascript DLR Engine是CodePlex上的一個開源項目,它是構建在DLR上層的Javascript引擎,還有RemObjects
也是如此。需要注意的是,Javascript DLR Engine只是對ECMAScript 3語言部分特性的實現。下面是我Download的Javascript DLR Engine項目截圖:

Javascript DLR Engine

這里大概講講流程:

1.首先,將JavaScriptContext這個自定義的LanguageContext注冊到ScriptRuntime

2.接着,獲取ScriptEngine對象,由於會將ScriptEngine對象緩存在ScriptRuntime對象中,所以第一次需要new一個ScriptEngine對象並緩存

3.接着,創建ScriptScope對象(相當於一個命名空間)

4.通過調用ScriptEngine.ExecuteFile方法執行腳本文件,內部會去調用JavaScriptContext的CompileSourceCode重寫方法獲取ScriptCode對象

5.在JavaScriptContext的CompileSourceCode的重寫方法中,使用ANTLRL來解析成AST(Expression Tree),用Expression構造一個名為InterpretedScriptCode的ScriptCode對象

6.接着調用InterpretedScriptCode對象的Run方法,然后交由Interpreter類去處理執行表達式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM