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:
class Program
{
static void Main(string[] args)
{
dynamic x = 3;
Console.WriteLine(x);
x = "Hello world";
Console.WriteLine(x);
x = new Item_mast()
{ ItemId=1,ItemDesсription="Pen",Cost=10 };
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 - 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:
dynamic x = 3;
to:
object x = 3
And we have the same result.
However, there are differences with the object
type as well. For example:
object obj = 24;
dynamic dyn = 24;
obj += 4;
dyn += 4;
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:
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:
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 - 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:
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);
viewbag.IncrementCost = (Action<int>)(x => viewbag.Cost += x);
viewbag.IncrementCost(6);
Console.WriteLine($"{viewbag.ItemId} ;
{viewbag.ItemDesсription} ; {viewbag.Cost}");
Console.ReadLine();
The result in 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:
class Item_mast : DynamicObject
{
Dictionary<string, object> members = new Dictionary<string, object>();
public override bool TrySetMember(SetMemberBinder binder, object value)
{
members[binder.Name] = value;
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
if (members.ContainsKey(binder.Name))
{
result = members[binder.Name];
return true;
}
return false;
}
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:
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.
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.
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:
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 - Adding DLR package
Next, add IronPython (Image 5):
Image 5 - Adding IronPython package
Let's add the simplest code, and we already use python:
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 - 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:
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