C#之动态语言扩展
DLR
在.NET Framework中,DLR2位于System.Dynamic
命名空间和System.Runtime.CompilerServices
命名空间的几个类中。
dynamic 类型
可以发现staticPerson
出现了编译错误,而dynamicPerson
并没有,因为定义为dynamic
的对象可以在运行期改变其类型,甚至改变多次,这与强制转换类型是不一样的,它在编译期不会做检查。
对于dynamic 类型有两个限制:动态对象不支持扩展方法,匿名函数(lambda表达式)也不能用于动态方法调用参数,因此LINQ不能用于动态对象。大多数LINQ调用都是扩展方法,而lambda表达式用作这些扩展方法的参数。
包含DLR ScriptRuntime
项目通过NuGet安装IronPython
,然后using Microsoft.Scripting.Hosting
,就有了ScriptRuntime
,
就可以执行存储在文件中 的代码段或完整的脚本。
例:
xaml文件
<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 的交互逻辑
/// </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();
}
}
}
从代码可知,ScriptRuntime
对象是通过配置文件来产生的,因为调用了ScriptRuntime.CreateFromConfiguration()
方法产生的,因此,本项目的App.config
如下:
<?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>
Calc Discount
按钮的click事件调用的方法是通过依次创建ScriptRuntime
,ScriptEngine
,ScriptSource
,ScriptScope
对象,对ScriptSource
进行执行,从ScriptScope
设定变量的值,获取变量的值。
Cal Tax
按钮的click事件调用的 方法是创建ScriptRuntime
,然后通过该对象的UseFile
方法,得到Dynamic
类型对象,然后通过该动态对象,调用脚本中的方法。
CountDisc.py
discCount=5
discAmt=0.1
retAmt=amt
if(prodCount>discCount):
retAmt=amt-(amt*discAmt)
AmountDisc.py
discAmt=0.25
retAmt=amt
if amt>25:
retAmt=amt-(amt*discAmt)
CalTax.py
def CalcTax(amount):
return amount*1.075
DynamicObject和ExpandoObject
- DynamicObject
创建自己的动态对象,要么从DynamicObject中派生,也要么使用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不用重写方法,可以直接使用,如果需要控制动态对象中属性的添加和访问,则使用DynamicObject是最佳选择,其他情况,则使用dynamic或者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 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
利用ExpandoObject,来储存任意数据类型的数据的一个Demo:
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