Dynamic Features in C#

Posted by furiousweebee on Tue, 23 Jul 2019 18:26:35 +0200

Original Link: http://www.cnblogs.com/jellochen/p/The-Dynamic-Feature-in-CSharp.html

As we all know, C#is a static language just like Java.Before C# 4.0, you want to and Dynamic Language Convenient interoperability (such as Python, Javascript, and so on) is not easy.The dynamic keyword C# 4.0 brings to us makes it easy for us to interoperate with dynamic languages.This article will elaborate from the following conveniences:

  1. 1. Use of dynamics
  2. 2.dynamic Principle (DLR)
  3. 3. Dynamic Behavior Implementation
  4. 4. Instance profiling: Javascript DLR Engine

1. Use of dynamics

There are two main examples of using dynamic:

Example 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();
}

Normally, int s =? + "3"; this compilation does not work because a string type cannot be implicitly converted to an int type.However, when? Is a dynamic type, this code can be compiled and passed, but at run time an exception is reported that the type "string" cannot be implicitly converted to "int".This seems to defer compile-time errors to runtime.

Example 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;
}

An anonymous type object is declared first, then passed as an argument to the Calculate method.The Calculate method takes a parameter of type dynamic and adds it.This achieves the effect of manipulating an implicit type.

2. dynamic Principle (DLR)

In the example above, we simply feel the power of the lower dynamic keyword.There are always things behind a powerful thing that support its development, and dynamic is no exception. DLR The (Dyanmic Language Runtime) library is the backing.

Above is the frame structure diagram of the DLR.Here's a simple example of how dynamic works:

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; }
}

By decompiling, the code is as follows:

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;
    }

We see that the compiler generates an o_u SiteContainer1 class for us, which contains four CallSite s:

  1. <>p_u Site2: corresponds to the first Console.WriteLine(dynamic)
  2. <>p_u Site3: corresponds to dynamic.ID
  3. <>p_u Site4: corresponds to the second Console.WriteLine(dynamic)
  4. <>p_u Site5: corresponding to dynamic.Name

The general steps are as follows:

  1. Change variables declared by dynamic to object types
  2. Parse the expression and execute it, construct CallSite with Binder, and internally implement it with Expression Tree.Expression Tree can be compiled to IL and then sent to CLR to compile to Native Code

The DLR uses a three-level cache, including L0, L1, and L2.Caching stores information in different scopes in different ways.Each call point contains its own L0 and L1 caches.L2 caches can be shared by multiple similar call points.Take the example above:

  1. The first time you construct <>p_u Site2, CallSite is constructed from CallSite <Action <CallSite, Type, object >.Create (CallSiteBinder), while L0 caches dedicated delegates based on Site History, which can be obtained from CallSite.Target.
  2. When the CallSite.Target delegate is called, the UpdateAndExecute * method of UpdateDelegates is called, which updates the cache and executes the delegate.
  3. The L1 cache caches the history (rules) of the dynamic site, an array of delegates, the L2 cache caches all rules generated by the same binder, a dictionary, Key is the delegate, Value is the RuleCache, and T is the delegate type.

The following experiments can be done:

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());
            }
        }

The test results are as follows:

3. Dynamic Behavior Implementation

To implement dynamic behavior in C#you need to implement the IDynamicMetaObjectProvider interface, which also provides two default implementations in the DLR: ExpandoObject and DynamicObject.

3.1ExpandoObject

The ExpandoObject class can operate dynamically at runtime (including adding, deleting, assigning, and evaluating) members, and by implementing the IDynamicMetaObjectProvider interface, it can share instances of the ExpandoObject class between languages that support the DLR interoperability model.

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);
    }
}

The results are as follows:

3.2DynamicObject

The interaction between the DynamicObject class and the DLR is more granular than the ExpandoObject class, which defines what operations can be performed on dynamic objects and how.Since its constructor is Protected, it cannot be directly new and needs to be inherited.

// 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);
    }

The results are as follows:

3.3IDynamicMetaObjectProvider

You can use the ExpandoObject class if you are simply doing dynamic operations at runtime; if you want to go a little deeper, define what operations can be performed on the dynamic objects and how to do them, you can use DynamicObject; if you want more complete control over the behavior of the dynamic objects, you canIDynamicMetaObjectProvider interface.Using the IDynamicMetaObjectProvider interface is a lower level of dependency on DLR libraries than the DynamicObject class.DLR uses an extensible expression tree to implement dynamic behavior.

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();
        }
    }

The call is as follows:

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

The result is shown in the figure:

4. Instance profiling: Javascript DLR Engine

Javascript DLR Engine Is an open source project on CodePlex, a Javascript engine built on top of the DLR, and RemObjects
The same is true.It is important to note that the Javascript DLR Engine is only an implementation of some of the features of the ECMAScript 3 language.Here's my Download Javascript DLR Engine Project Screenshot:

Here is a general description of the process:

1. First, register the JavaScriptContext, a custom LanguageContext, with the ScriptRuntime

2. Next, get the ScriptEngine object. Since the ScriptEngine object is cached in the ScriptRuntime object, you need a new ScriptEngine object for the first time and cache it

3. Next, create a ScriptScope object (equivalent to a namespace)

4. By calling the ScriptEngine.ExecuteFile method to execute the script file, the JavaScriptContext's CompileSourceCode override method is called internally to get the ScriptCode object

5. In the override method of JavaScriptContext's CompileSourceCode, ANTLRL is used to parse into AST (Expression Tree) and Expression is used to construct a criptCode object named InterpretedScriptCode

6. The Run method of the InterpretedScriptCode object is then called, and the execution expression is handled by the Interpreter class

Reproduced at: https://www.cnblogs.com/jellochen/p/The-Dynamic-Feature-in-CSharp.html

Topics: Javascript Java Python ECMAScript