Dynamic Language Extensions for C#

Posted by Shane10101 on Fri, 04 Feb 2022 18:20:45 +0100

DLR

In. In the NET Framework, DLR2 is located in System.Dynamic Namespace and System. Runtime. In several classes of the CompilerServices namespace.

dynamic type

You can see that staticPerson has compiled errors, but dynamicPerson does not because objects defined as dynamic can change their type at run time or even more than once, unlike cast types, which do not check at compile time.

There are two limitations to the dynamic type: dynamic objects do not support extension methods, and anonymous functions (lambda expressions) cannot be used for dynamic method call parameters, so LINQ cannot be used for dynamic objects. Most LINQ calls are extension methods, and lambda expressions are used as parameters to these extension methods.

Contains DLR ScriptRuntime

The project installs IronPython through NuGet, then uses Microsoft. Scripting. Hosting, there's a ScriptRuntime,

You can execute code snippets stored in files or complete scripts.

Example:

xaml file

<Window x:Class="DiscountWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DiscountWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="300">
    <Grid AutomationProperties.HelpText="disc based on cost">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <RadioButton x:Name="CostRadioButton" Grid.Row="0" VerticalAlignment="Center" >Disc Based on Cost</RadioButton>
        <RadioButton x:Name="NumRadioButton" Grid.Row="1" VerticalAlignment="Center" >Disc Based on No of Items</RadioButton>
        <StackPanel Grid.Row="2" Orientation="Horizontal" VerticalAlignment="Center">
            <TextBlock>Total No of Items:</TextBlock>
            <TextBox x:Name="totalItems" Width="180" HorizontalContentAlignment="Right"/>
        </StackPanel>
        <StackPanel Grid.Row="3" Orientation="Horizontal" VerticalAlignment="Center">
            <TextBlock>Total Amount</TextBlock>
            <TextBox x:Name="totalAmount" Width="178" HorizontalAlignment="Left" VerticalAlignment="Center" HorizontalContentAlignment="Right"/>
        </StackPanel>
        <StackPanel Grid.Row="7" Orientation="Vertical" VerticalAlignment="Center">
            <Button x:Name="calDisc" Content="Calc Discount" Width="100" Click="calDisc_Click"/>
            <Button x:Name="calTax" Content="Cal  Tax" Width="100" Margin="0,10,0,0" Click="calTax_Click"/>
        </StackPanel>
        <StackPanel Grid.Row="4" Orientation="Horizontal" VerticalAlignment="Center">
            <TextBlock>Discounted Amount:</TextBlock>
            <TextBlock x:Name="label"></TextBlock>
        </StackPanel>
        <StackPanel Grid.Row="5" Orientation="Horizontal" VerticalAlignment="Center">
            <TextBlock>Amount with Tax:</TextBlock>
            <TextBlock x:Name="labelA"></TextBlock>
        </StackPanel>
    </Grid>
</Window>

using Microsoft.Scripting.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace DiscountWPF
{
    /// <summary>
    /// MainWindow.xaml's interaction logic
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

       
        private void calDisc_Click(object sender, RoutedEventArgs e)
        {
            string scriptToUse;
            if (CostRadioButton.IsChecked.Value)
            {
                scriptToUse = "AmountDisc.py";
            }
            else
            {
                scriptToUse = "CountDisc.py";
            }
            ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
            ScriptEngine pythEng = scriptRuntime.GetEngine("python");
            ScriptSource source = pythEng.CreateScriptSourceFromFile(scriptToUse);
            ScriptScope scope = pythEng.CreateScope();
            scope.SetVariable("prodCount", Convert.ToInt32(totalItems.Text));
            scope.SetVariable("amt", Convert.ToDecimal(totalAmount.Text));
            source.Execute(scope);
            label.Text = scope.GetVariable("retAmt").ToString();
        }

        private void calTax_Click(object sender, RoutedEventArgs e)
        {
            ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
            dynamic calcRatet = scriptRuntime.UseFile("CalcTax.py");
            labelA.Text = calcRatet.CalcTax(Convert.ToDecimal(label.Text)).ToString();
        }
    }
}

From the code, the ScriptRuntime object is generated from a configuration file because the ScriptRuntime is called. The CreateFromConfiguration () method is generated, so App for this project. The config is as follows:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<configSections>
		<section name="microsoft.scripting" type="Microsoft.Scripting.Hosting.Configuration.Section, Microsoft.Scripting"/>
	</configSections>
	<microsoft.scripting>
		<languages>
			<language names="IronPython;Python;py" extensions=".py" displayName="Python" type="IronPython.Runtime.PythonContext, IronPython"/>
		</languages>
	</microsoft.scripting>
	<startup>
		<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
	</startup>
</configuration>

The click event of the Calc Discount button is called by creating a ScriptRuntime, ScriptEngine, ScriptSource,ScriptScope object, executing the ScriptSource, setting the value of the variable from the ScriptScope, and getting the value of the variable.

The click event of the Cal Tax button calls by creating a ScriptRuntime, then getting a Dynamic type object through the object's UseFile method, and then calling the method in the script through the dynamic object.

DynamicObject and ExpandoObject

  • DynamicObject

Create your own dynamic objects, either derived from DynamicObject or using ExpandoObject

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DynamicDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic wroxDyn = new WroxDynamicObject();
            wroxDyn.FirstName = "John";
            wroxDyn.LastName = "Yang";
            Console.WriteLine(wroxDyn.GetType());
            Console.WriteLine("{0}--{1}", wroxDyn.FirstName, wroxDyn.LastName);
            Func<DateTime, string> GetTomo = today => today.AddDays(1).ToShortDateString();
            wroxDyn.GetTomorrow = GetTomo;
            Console.WriteLine("Tomorrow is {0}", wroxDyn.GetTomorrow(DateTime.Now));
        }
    }
    class WroxDynamicObject : DynamicObject
    {
        Dictionary<string, object> _dynamicData = new Dictionary<string, object>();
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            bool success = false;
            result = null;
            if (_dynamicData.ContainsKey(binder.Name))
            {
                result = _dynamicData[binder.Name];
                success = true;
            }
            else
            {
                result = "Property not found";

            }
            return success;
        }
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            _dynamicData[binder.Name] = value;
            return true;
        }
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            dynamic method = _dynamicData[binder.Name];
            result = method((DateTime)args[0]);
            return result != null;
        }
    }
    

}

output

DynamicDemo.WroxDynamicObject
John--Yang
Tomorrow is 2022/2/5
  • ExpandoObject

ExpandoObject can be used directly without rewriting methods. If you need to control the addition and access of attributes in a dynamic object, DynamicObject is the best choice. Otherwise, dynamic or ExpandoObject is used.

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DynamicDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic wroxDyn = new ExpandoObject();
            wroxDyn.FirstName = "John";
            wroxDyn.LastName = "Yang";
            Console.WriteLine(wroxDyn.GetType());
            Console.WriteLine("{0}--{1}", wroxDyn.FirstName, wroxDyn.LastName);
            Func<DateTime, string> GetTomo = today => today.AddDays(1).ToShortDateString();
            wroxDyn.GetTomorrow = GetTomo;
            Console.WriteLine("Tomorrow is {0}", wroxDyn.GetTomorrow(DateTime.Now));
        }
    }
}

output

System.Dynamic.ExpandoObject
John--Yang
Tomorrow is 2022/2/5

A Demo that uses ExpandoObject to store data of any data type:

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DynamicDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var retList = new List<dynamic>();
            dynamic expObj=new ExpandoObject();
            ((IDictionary<string, object>)expObj).Add("john", 10);
            retList.Add(expObj);
            dynamic expObj1 = new ExpandoObject();
            ((IDictionary<string, object>)expObj1).Add("yang", DateTime.Now);
            retList.Add(expObj1);
            foreach(var dy in retList)
            {
                var tempDic = (IDictionary<string, object>)dy;
                foreach(var kv in tempDic)
                {
                    Console.WriteLine(kv.Key);
                    Console.WriteLine(kv.Value);
                }
            }



        }
    }

    

}

output

john
10
yang
2022/2/4 23:35:21

Topics: C#