zoukankan      html  css  js  c++  java
  • 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}:	{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类去处理执行表达式

  • 相关阅读:
    Optional类的基本使用(没怎么看)
    443. String Compression字符串压缩
    520. Detect Capital判断单词有效性
    521. Longest Uncommon Subsequence I 最长不同子数组
    459. Repeated Substring Pattern 判断数组是否由重复单元构成
    686. Repeated String Match 字符串重复后的子字符串查找
    696. Count Binary Substrings统计配对的01个数
    58. Length of Last Word最后一个单词的长度
    680. Valid Palindrome II 对称字符串-可删字母版本
    125. Valid Palindrome判断有效的有符号的回文串
  • 原文地址:https://www.cnblogs.com/jellochen/p/The-Dynamic-Feature-in-CSharp.html
Copyright © 2011-2022 走看看