Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages

Dynamic Language Runtime in C#/.NET

5.00/5 (8 votes)
30 Jan 2022CPOL8 min read 14.9K  
An overview of Dynamic Language Runtime DLR in C#
This article discusses how Dynamic Language Runtime works in C#, DynamicObject and ExpandoObject and the simplest use of IronPython in .NET.

Introduction

Although C# belongs to statically typed languages, some dynamic features have been added in recent versions of the language. In this article, I want to show how the Dynamic Language Runtime (DLR) works in C#, DynamicObject and ExpandoObject and the simplest use of IronPython in .NET.

Dynamic Language Runtime DLR in C#

The DLR (Dynamic Language Runtime) has been added since .NET 4.0 and represents the runtime environment of dynamic languages such as IronPython and IronRuby.

In order to understand the essence of this innovation, you need to know the difference between languages with static and dynamic typing. In languages with static typing, the identification of all types and their members - properties and methods occurs at the compilation stage, while in dynamic languages, the system does not know anything about the properties and methods of types until execution.

Thanks to this DLR environment, C# can create dynamic objects whose members are identified at the program execution stage and use them together with traditional objects with static typing.

The use of dynamic types is the key point of using DLR in C# and thus allows you to skip type checking at the compilation stage. Moreover, objects declared as dynamic can change their type during the program operation. For example:

C#
class Program
{
    static void Main(string[] args)
    {
            dynamic x = 3;     // here x is a integer
            Console.WriteLine(x);
 
            x = "Hello world"; // now x is a string
            Console.WriteLine(x);
 
            x = new Item_mast() 
                { ItemId=1,ItemDesсription="Pen",Cost=10 }; // now x is a Item_mast
            Console.WriteLine(x);
            Console.ReadLine();        
    }
}
  public class Item_mast
        {
            public int ItemId { get; set; }
            public string ItemDesсription { get; set; }
            public int Cost { get; set; }
 
            public override string ToString()
            {
                return ItemId.ToString() + ", "+ ItemDesсription + " " + Cost.ToString();
            }
        }

The result is shown in Image 1:

Image 1

Image 1 - Basic DLR example

Let's describe the code a little. Even though the variable x changes its type several times, this code will work fine. This is the key point of difference between dynamic and var. For a variable declared using the var keyword, the type is output at compile time and then does not change at runtime. In addition, you can notice some similarities between the dynamic type and the object type. We can easily replace the expression:

C#
dynamic x = 3;

to:

C#
object x = 3

And we have the same result.

However, there are differences with the object type as well. For example:

C#
object obj = 24;
dynamic dyn = 24;
obj += 4; // we can not do it!!!
dyn += 4; // now is ok

On the line obj += 4, we will see an error, since the += operation cannot be applied to the object and int types. With a variable declared as dynamic, this is possible, since its type will only be known at runtime.
It should be noted that dynamic can be applied not only to variables, but also to methods and properties. Let's make changes in class and consider the next example:

C#
public class Item_mast
        {
            public int ItemId { get; set; }
            public string ItemDesсription { get; set; }
            public dynamic Cost { get; set; } 
 
            public dynamic GetPrice(dynamic value, string format)
            {
                if (format == "string")
                {
                    return value + " dollar";
                }
                else if (format == "int")
                {
                    return value;
                }
                else
                {
                    return 0.0;
                }
            }

The Item_mass class defines a dynamic Cost property, so when setting a value for this property, we can write both Item.Cost=10.00 and Item.Cost="ten". Both options will be correct. There is also a GetPrice method that returns the dynamic value. For example, depending on the parameter, we can return either a string representation of the price or a numerical one. The method also takes dynamic as a parameter. Thus, we can pass both an integer and a fractional number as the income value. Let's look at the specific application:

C#
dynamic item1 = new Item_mast() { ItemId = 1, ItemDesсription = "Pen", Cost = 10 };
            Console.WriteLine(item1);
            Console.WriteLine(item1.GetPrice(10.00, "int"));
 
            dynamic item2 = new Item_mast() 
                            { ItemId = 2, ItemDesсription = "Pencil", Cost = "five" };
            Console.WriteLine(item2);
            Console.WriteLine(item2.GetPrice(5, "string"));
            Console.ReadLine();

As the result, we will have (Image 2):

Image 2

Image 2 - The result of using dynamic variable example

In this part, we examined the use of dynamic types with examples.

DynamicObject and ExpandoObject

ExpandoObject

C#/.NET development has the ability to create dynamic objects very similar to what is used in JavaScript. This possibility is provided by the use of the namespace Dynamic and in particular, the ExpandoObject class.

Let's consider an example:

C#
dynamic viewbag = new System.Dynamic.ExpandoObject();
            viewbag.ItemId = 1;
            viewbag.ItemDesсription = "Pen";
            viewbag.Cost = 10;

            viewbag.Categories = new List<string> { "Flex", "Soft", "Luxury" };

            Console.WriteLine($"{viewbag.ItemId} ; 
                             {viewbag.ItemDesсription} ; {viewbag.Cost}");
            foreach (var cat in viewbag.Categories)
                Console.WriteLine(cat);

            //declare method
            viewbag.IncrementCost = (Action<int>)(x => viewbag.Cost += x);
            viewbag.IncrementCost(6); // Increase Cost 
            Console.WriteLine($"{viewbag.ItemId} ; 
                                {viewbag.ItemDesсription} ; {viewbag.Cost}");

            Console.ReadLine();

The result in Image 3:

Image 3

Image 3 - Example of using ExpandoObject()

A dynamic ExpandoObject object can have any properties declared that can represent a variety of objects. You can also set methods using delegates.

DynamicObject

The DynamicObject class is quite similar to ExpandoObject. However, in the case of DynamicObject, we need to create our own class by inheriting it from DynamicObject and implementing its methods:

  • TryBinaryOperation(): Performs a binary operation between two objects. Equivalent to standard binary operations, for example, the addition of x + y)
  • TryConvert(): Performs conversion to a specific type. Equivalent to the basic conversion in C#, for example, (SomeType) obj
  • TryCreateInstance(): Creates an instance of an object
  • TryDeleteIndex(): Removes the indexer
  • TryDeleteMember(): Deletes a property or method
  • TryGetIndex(): Gets the element by index through the indexer. In C#, it can be equivalent to the following expression int x = collection[i]
  • TryGetMember(): Getting the value of the property. Equivalent to accessing a property, for example, string n = item1.ItemDescription
  • TryInvoke(): Calling an object as a delegate
  • TryInvokeMember(): Method call
  • TrySetIndex(): Sets the element by index through the indexer. In C#, it can be equivalent to the following expression collection[i] = x;
  • TrySetMember(): Sets the property. Equivalent to assigning an item value to a property.Itemdescription = "Pen"
  • TryUnaryOperation(): Performs a unary operation similar to unary operations in C#: x++

Each of these methods has the same detection model: they all return a boolean value indicating whether the operation was successful. As the first parameter, they all take a binder or binder object. If the method represents a call to an indexer or an object method that can accept parameters, then the object[] array is used as the second parameter - it stores the arguments passed to the method or indexer.

Almost all operations, except for setting and deleting properties and indexers, return a certain value (for example, if we get the value of a property. In this case, the third parameter out object value is used, which is intended to store the returned object.

Let's take an example by creating a dynamic object class:

C#
class Item_mast : DynamicObject
        {
            Dictionary<string, object> members = new Dictionary<string, object>();
 
            // set prop
            public override bool TrySetMember(SetMemberBinder binder, object value)
            {
                members[binder.Name] = value;
                return true;
            }
            // get prop
            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                result = null;
                if (members.ContainsKey(binder.Name))
                {
                    result = members[binder.Name];
                    return true;
                }
                return false;
            }
            // call method
            public override bool TryInvokeMember
            (InvokeMemberBinder binder, object[] args, out object result)
            {
                dynamic method = members[binder.Name];
                result = method((int)args[0]);
                return result != null;
            }
        }

We cannot directly create objects from DynamicObject, so our Item_mast class is a child. In our class, we redefine three methods. We also use Dictionary<string, object> members to store all class members, but we also use it for properties and methods. In this dictionary, the keys here are the names of properties and methods, and the values are the values of these properties.
Using the TrySetMember() method, we set the properties:

C#
bool TrySetMember(SetMemberBinder binder, object value)

Here, the binder parameter stores the name of the property to be set (binder.Name), and value is the value that it needs to set.
TryGetMember is an overridden method that we use to get the property value.

C#
bool TryGetMember(GetMemberBinder binder, out object result)

Again, the binder contains the name of the property, and the result parameter will contain the value of the resulting.

C#
The TryInvokeMember method is defined for calling methods:

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
    dynamic method = members[binder.Name];
    result = method((int)args[0]);
    return result != null;
}

First, using binder, we get the method and then pass it the args[0] argument, first casting it to the int type, and set the result of the method in the result parameter. That is, in this case, it is assumed that the method will take one parameter of the int type and return some result. Let's take an example of using a class in our application:
Now let's apply the class in the program:

C#
static void Main(string[] args)
{
   dynamic item = new Item_mast();
            item.ItemId = 1;
            item.ItemDesсription = "Pen";
            item.Cost = 10;
            Func<int, int> Incr = delegate (int x) { item.Cost += x; return item.Cost; };
            item.IncrementCost = Incr;
            Console.WriteLine($"{item.ItemId} ; {item.ItemDesсription} ; {item.Cost}");         
            item.IncrementCost(6);
            Console.WriteLine($"{item.ItemId} ; {item.ItemDesсription} ; {item.Cost}");
            Console.ReadLine();
}

The expression item.ItemId = 1 and item.ItemDescription = "Pen" will call the TrySetMember method, to which a number will be passed as the second parameter in the first variant, and the string "Pen" will be passed in the second variant.
The return item.Cost calls the TryGetMember method.
Also, the item object has an IncrementCost method defined, which represents the actions of an anonymous delegate delegate (int x) { item.Cost+=x; return item.Cost; }. The delegate takes the number x, increases the Cost property by this number and returns the new value item.Cost. And when calling this method, the TryInvokeMember method will be accessed. And, thus, the value of the item.Cost property will be incremented.

The advantage is that you can redefine the behavior of a dynamic object when acting with it, i.e., actually make your own implementation of a dynamically expandable object.

In this part, we examined the use of DynamicObject and ExpandoObject with examples.

Using IronPython in .NET.

It would seem that why do we need more languages, especially those that are used within another C# language?However, one of the key points of the DLR environment is the support of languages such as IronPython and IronRuby. This can be useful in writing functional client scripts. It can even be said that the creation of client scripts is widespread these days, many programs and even games support the addition of client scripts written in various languages. In addition, there may be Python libraries whose functionality may not be available in .NET. And in this case, again, IronPython can help us.

Let's look at an example. First, we need to add all the necessary NuGet packages. For this, I will use a batch manager. First, let's add the DLR package (Image 4).

Image 4

Image 4 - Adding DLR package

Next, add IronPython (Image 5):

Image 5

Image 5 - Adding IronPython package

Let's add the simplest code, and we already use python:

C#
class Program
    {
        static void Main(string[] args)
        {
            ScriptEngine engine = Python.CreateEngine();
            engine.Execute("print 'hello, world'");
        }
    }

As the result, we have (Image 6):

Image 6

Image 6 - Using IronPyton

Here, the Python expression, print 'hello, world' is used, which outputs a string to the console. To create an engine that executes a script, the ScriptEngine class is used. And its Execute() method executes the script.
Moreover, we could create a file, for example helloword.py and paste the contents of the file directly into our code:

C#
engine.ExecuteFile("D://helloword.py");

Also, the ScriptScope object allows you to interact with the script by receiving or installing it. However, this is already beyond the scope of this article.

Conclusion

In conclusion, we looked at how the Dynamic Language Runtime (DLR) works in C#, how to use DynamicObject and ExpandoObject and how IronPython works on the simplest example in .NET.

History

  • 31st January, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)