开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第22天,点击查看活动详情
尽管 C# 属于静态类型语言,但是一些动态特性很早就有了。本篇将会介绍动态语言运行时(Dynamic Language Runtime
——DLR
)如何在C#中工作、DynamicObject
and ExpandoObject
,以及 .NET 中 IronPython 的最简单实现。
C# 中的动态语言运行时
.NET 4.0 开始,添加了 DLR (Dynamic Language Runtime) ,并有了动态语言的运行时环境,如 IronPython 和 IronRuby
为了能够理解其本质,我们需要知道静态和动态类型语言之间的不同,静态类型语言中,所有类型及其成员属性和方法的标识发生在编译阶段,但是对于动态语言,系统在执行之前,无法确定关于类型的属性和方法的任何情况。
借助于 DLR 环境,C#可以创建其成员在程序执行阶段被标识的动态对象,并可以与传统的静态类型对象一起使用。
在C#中使用 DLR 是使用动态类型的关键,这允许在编译阶段跳过类型检查。此外,对象声明为动态的,可以在程序操作期间改变其类型。(即 弱类型语言,也就是 C# 的 DLR 是动态弱类型语言环境)
强类型语言在变量类型确定后不允许修改。
下面使用 DLR 的示例:
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();
}
}
输出结果如下:
3
Hello world
1, Pen 10
即使变量x
改变了其类型好几次,代码依然运行成功,这也是 dynamic
和 var
两个关键字的不同。使用 var
关键字的变量声明,其类型在编译时确定,并且在运行时不能更改。
此外,你可能注意到 dynamic
类型 和 object
类型 之间的一些相似,看下面的表达式:
dynamic x = 3;
object x = 3
两个的结果一样。但是,两者是不同的。比如下面:
object obj = 24;
dynamic dyn = 24;
obj += 4; // we can not do it!!! [不能这么操作]
dyn += 4; // now is ok
obj += 4
行我们将会看到一个错误,因为 object
和 int
类型 不能使用 +=
操作。使用 dynamic
声明的变量是可以这么操作的,因为其类型只有在运行时才知道。
需要注意的是,dynamic
不仅可以用于变量,也可以用于方法和属性。考虑下面改变后的例子:
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;
}
}
}
Item_mast
类定义了一个动态的Cost
属性,因此当为其设置值时,可以是 Item.Cost=10.00
和 Item.Cost="ten"
,两者都正确。GetPrice
是返回动态值的方法,如示例,取决于参数,我们可以返回表示价格的string
或数字,方法还接受一个动态变量参数,因此可以传递一个整数或小数数字作为输入值。下面是相关的使用: 【fractional number
小数】
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();
其运行结果如下:
1, Pen 10
10
2, Pencil five
5 dollar
DynamicObject 和 ExpandoObject
ExpandoObject
C#/.NET
开发可以创建,与在JavaScript中使用的非常相似的动态对象,其实现是通过使用Dynamic
命名空间,特别是ExpandoObject
类提供的。
考虑如下的示例:
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();
其结果如下:
1 ; Pen ; 10
Flex
Soft
Luxury
1 ; Pen ; 16
一个动态的ExpandoObject
对象可以声明任何表示各种对象的属性,也可以使用委托设置方法。
DynamicObject
DynamicObject
类与ExpandoObject
非常相似,但是,使用DynamicObject
,我们需要通过继承它并实现其方法来创建我们自己的类:
TryBinaryOperation()
: 两个对象之间执行二进制操作. 等效于标准二进制运算,例如x + y
的加法。TryConvert()
: 执行到特定类型的转换. 等效于C#中的基本转换,例如(SomeType) obj
。TryCreateInstance()
: 创建一个对象的实例TryDeleteIndex()
: 移除索引器TryDeleteMember()
: 删除属性或方法TryGetIndex()
: 通过索引器获取指定索引的元素. 在C#中,这与int x = collection[i]
表达式是等效的。TryGetMember()
: 获取属性值. 等效于访问属性,例如string n = item1.ItemDescription
TryInvoke()
: 作为委托调用一个对象TryInvokeMember()
: 方法调用TrySetIndex()
: 通过索引器的索引设置元素. 在C#中,这与collection[i] = x
表达式是等效的;TrySetMember()
: 设置属性. 等效于property.Itemdescription = "Pen"
为 Item 分配值。TryUnaryOperation()
: 执行一个类似于 C#x++
的一元运算。
所有这些方法都有相同的监测模型:均返回一个表示是否操作成功的布尔值,作为第一个参数,他们都使用绑定器或绑定器对象;如果方法表示一个索引器或一个可接受参数的对象方法的调用,则object[]
数组用作第二个参数——存储传递到方法或索引器的参数。
除了设置和删除属性和索引器之外,所有操作都返回一个确定值。例如,如果我们获取一个属性值,下面的例子,第三个参数out
对象的值被使用,该对象用于存储返回的object
。
下面是创建 dynamic
对象的示例:
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;
}
}
不能直接从 DynamicObject
创建对象,所以 Item_mast
类做为子类使用,里面重定义了三个方法,使用 Dictionary<string, object>
存储所有的类成员,包含属性和方法,其 keys
是属性和方法的名字,values
是属性的值。
使用 TrySetMember
方法设置属性:
bool TrySetMember(SetMemberBinder binder, object value)
此处,binder 参数存储属性名,通过(binder.Name)
设置,value 参数是需要设置的值。
TryGetMember
重写方法用于获取属性值。
bool TryGetMember(GetMemberBinder binder, out object result)
同样,binder 包含属性名,result 参数将包含结果值。
TryInvokeMember
用做调用方法
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
dynamic method = members[binder.Name];
result = method((int)args[0]);
return result != null;
}
首先使用 binder 我们获取方法,并传递 转换为 int 类型的 args[0]
参数,设置 result
参数作为方法的结果。也就是说,在此例中,方法将会使用一个 int 类型的参数并返回结果。
在程序中使用这个类作为示例:
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();
}
item.ItemId = 1
和 item.ItemDescription = "Pen"
表达式将会调用 TrySetMember
方法,第一个表达式将会传递一个数字、第二个表达式将传递字符串 "Pen" 作为第二个参数,
返回 item.Cost
将调用 TryGetMember
方法。
同时,item
对象有一个 IncrementCost
方法定义,它表示匿名委托 elegate (int x) { item.Cost += x; return item.Cost; }
,该委托方法使用数字x
,将其相加到 Cost
属性,并返回 item.Cost
新值。当调用该方法时,将会访问 TryInvokeMember
方法,item.Cost
属性的值也将会增加。
使用 DynamicObject
的最大优势是你可以重定义动态类型的行为,例如,在实际中对动态可扩展对象,创建你自己的实现。
在 .NET 中 使用 IronPython
我们似乎会问为什么需要更多的语言,尤其是使用那些另一种的C#语言?DLR环境的一个关键点是对诸如 IronPython
和 IronRuby
语言的支持,这在编写功能性的客户端脚本时非常有用,甚至可以说当今创建客户端脚本非常普遍,许多程序甚至游戏,都支持不同语言编写的额外的客户端脚本。此外,可能存在Python库的功能在 .NET 中不可用。这些情况下,IronPython
可以帮助我们。
对于使用示例,我们首先需要添加两个NuGet
包:DLR
和 IronPython
。
添加下面最简单的代码,用来使用 Python :
class Program
{
static void Main(string[] args)
{
ScriptEngine engine = Python.CreateEngine();
engine.Execute("print 'hello, world'");
}
}
将会看到如下结果:
hello, world
此处的Python表达式print 'hello, world'
被使用,并输出一个字符串到控制台。ScriptEngine
类表示创建的执行脚本的引擎,Execute()
方法则执行脚本。
此外,我们可以创建一个python文件并执行它。比如 helloword.py
的python文件,并直接将代码添加到文件内容:
engine.ExecuteFile("D://helloword.py");
而 ScriptScope
对象允许通过接受和安装py脚本,实现交互执行。