C# 4.0引入了一个叫做“dynamic”的新类型。这听起来不错,但程序员有什么用处呢?
是否存在它能挽救局面的情况?
C# 4.0引入了一个叫做“dynamic”的新类型。这听起来不错,但程序员有什么用处呢?
是否存在它能挽救局面的情况?
dynamic
关键字是在C# 4.0版本中新增的一个特性,同时还加入了许多其他新功能,以使其更容易与来自其他运行时并具有不同API的代码进行交互。Word.Application
对象,并且想要打开一个文档,则执行此操作的方法至少需要15个参数,其中大多数是可选的。object missing = System.Reflection.Missing.Value;
object fileName = "C:\\test.docx";
object readOnly = true;
wordApplication.Documents.Open(ref fileName, ref missing, ref readOnly,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing);
注意所有这些参数?你需要传递它们,因为在C# 4.0之前,没有可选参数的概念。通过引入以下内容,使得与COM API更易于工作:
ref
变成可选的上述调用的新语法将是:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
看看它变得多么容易,变得更加可读了吗?
让我们把它分解一下:
named argument, can skip the rest
|
v
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
^ ^
| |
notice no ref keyword, can pass
actual parameter values instead
C#编译器现在的神奇之处在于它会注入必要的代码,并与运行时中的新类一起工作,几乎做与以前相同的事情,但语法已经对您隐藏了,现在您可以专注于what,而不是太过关注how。Anders Hejlsberg喜欢说您必须调用不同的"咒语",这是一种关于整个事情的魔力的双关语,其中您通常需要挥动手并按正确的顺序说出一些魔法话语才能启动某种类型的咒语。与COM对象交互的旧API方式也是如此,您需要跳过很多障碍才能让编译器为您编译代码。
在C# 4.0之前,如果您尝试与没有接口或类的COM对象交互,事情会更加复杂,您只有一个IDispatch
引用。
如果您不知道它是什么,IDispatch
基本上就是COM对象的反射。使用IDispatch
接口,您可以询问对象:"Save方法的ID号码是多少",并构建包含参数值的特定类型的数组,最后调用IDispatch
接口上的Invoke
方法来调用方法,传递您成功搜集到的所有信息。
上面的Save方法可能看起来像这样(这绝对不是正确的代码):
string[] methodNames = new[] { "Open" };
Guid IID = ...
int methodId = wordApplication.GetIDsOfNames(IID, methodNames, methodNames.Length, lcid, dispid);
SafeArray args = new SafeArray(new[] { fileName, missing, missing, .... });
wordApplication.Invoke(methodId, ... args, ...);
只是为了打开一个文档而已。
很久以前,VB就拥有可选参数和大多数支持这一点的功能,所以这段C#代码:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
在表达能力方面,dynamic 关键字基本上是 C# 赶上 VB 的步伐,但是通过使其可扩展并且不仅局限于 COM 来实现。当然,这也适用于 VB.NET 或任何其他建立在 .NET 运行时之上的语言。
如果您想要了解有关 IDispatch 接口的更多信息,请访问 Wikipedia: IDispatch,以获取更详细的阅读内容。它确实是一些非常复杂的东西。
但是,如果您想与 Python 对象交互怎么办?与用于 COM 对象的 API 不同,这里有一个不同的 API,由于 Python 对象的动态性质,您需要使用反射机制来查找正确的方法调用、参数等,而不是使用 .NET 反射,这与上面的 IDispatch 代码类似,但完全不同。
那么对于 Ruby 呢?还有另一个不同的 API。
JavaScript 呢?同样需要不同的 API。
dynamic 关键字由两部分组成:
dynamic
dynamic
关键字需要这些类来映射调用方式。该 API 甚至有文档说明,所以如果您有从未涉及的运行时的对象,则可以添加它。然而,dynamic
关键字并不意味着要取代任何现有的 .NET-only 代码。当然,您可以这样做,但是它并不是出于这个原因而被添加的,C# 编程语言的作者,Anders Hejlsberg 等人最坚定的立场仍是将 C# 视为一种强类型编程语言,并且不会牺牲这个原则。
这意味着尽管您可以编写如下代码:
dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;
它的编译不是作为一种魔法,也不是在运行时解析出你的意图。
它的目的是使得与其他类型的对象交互更加容易。
网上有很多关于关键字的材料,支持者、反对者、讨论、抱怨、赞美等等。
我建议您从以下链接开始,然后搜索更多:
dynamic
,以支持其他生态系统中类似反射的方法调用方式,并提供一种黑盒子方法来处理数据结构,并提供实现的文档化方式。 - Lasse V. Karlsen动态关键字是C# 4.0中的新内容,用于告诉编译器一个变量的类型可以改变或者在运行时才能确定。可以把它想象成无需强制转换就能与对象进行交互。
dynamic cust = GetCustomer();
cust.FirstName = "foo"; // works as expected
cust.Process(); // works as expected
cust.MissingMethod(); // No method found!
请注意,我们不需要将cust强制转换为Customer类型,也不需要声明它的类型。因为我们将其声明为动态类型,运行时会接管并搜索并设置FirstName属性。当然,使用动态变量时,您放弃了编译器类型检查。这意味着调用cust.MissingMethod()将编译但直到运行时才失败。此操作的结果是RuntimeBinderException,因为Customer类未定义MissingMethod。
上面的示例展示了在调用方法和属性时dynamic的工作方式。另一个强大(但潜在危险)的功能是能够为不同类型的数据重复使用变量。我相信Python、Ruby和Perl程序员可以想出无数利用这一点的方法,但我使用C#已经很长时间了,所以这种做法对我来说感觉“不对”。
dynamic foo = 123;
foo = "bar";
好的,所以你很可能不会经常编写类似上面的代码。然而,有时变量重用可能会有用,或者可以清理一些脏的旧代码。我经常遇到的一个简单情况是不得不在十进制和双精度之间不断地进行类型转换。
decimal foo = GetDecimalValue();
foo = foo / 2.5; // Does not compile
foo = Math.Sqrt(foo); // Does not compile
string bar = foo.ToString("c");
第二行无法编译,因为2.5被定义为double类型,并且第三行无法编译,因为Math.Sqrt需要一个double类型的参数。显然,你只需要进行强制转换和/或更改变量类型,但有些情况下使用dynamic可能更合适。
dynamic foo = GetDecimalValue(); // still returns a decimal
foo = foo / 2.5; // The runtime takes care of this for us
foo = Math.Sqrt(foo); // Again, the DLR works its magic
string bar = foo.ToString("c");
阅读更多功能:http://www.codeproject.com/KB/cs/CSharp4Features.aspx
dynamic
来解决可以用标准 C# 特性和静态类型甚至是类型推断(var
)来解决的问题,而且即使使用 dynamic
解决问题也可能比这些特性更劣。dynamic
应当仅限于与 DLR 进行互操作时使用。如果你在一个像 C# 这样的静态类型语言中编写代码,那就直接使用静态类型进行编码,而不要模仿动态语言,那样只会让代码变得难看。 - Philip Daubmeierdynamic
变量,而实际上并不需要它们(例如在求平方根的示例中),这将放弃干净的编译时错误检查,而是可能导致运行时错误。 - Philip Daubmeier我很惊讶没有人提到多重分派。通常解决这个问题的方法是通过访问者模式,但这并不总是可能的,所以你最终会得到堆叠的is
检查。
以下是我自己应用的一个真实例子。不要这样做:
public static MapDtoBase CreateDto(ChartItem item)
{
if (item is ElevationPoint) return CreateDtoImpl((ElevationPoint)item);
if (item is MapPoint) return CreateDtoImpl((MapPoint)item);
if (item is MapPolyline) return CreateDtoImpl((MapPolyline)item);
//other subtypes follow
throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}
你做:
public static MapDtoBase CreateDto(ChartItem item)
{
return CreateDtoImpl(item as dynamic);
}
private static MapDtoBase CreateDtoImpl(ChartItem item)
{
throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}
private static MapDtoBase CreateDtoImpl(MapPoint item)
{
return new MapPointDto(item);
}
private static MapDtoBase CreateDtoImpl(ElevationPoint item)
{
return new ElevationDto(item);
}
请注意,在第一种情况下,ElevationPoint
是 MapPoint
的子类,如果它不放在 MapPoint
之前,则永远无法到达。对于动态方法调用,情况并非如此,因为会调用最匹配的方法。ChartItem
对象中添加无用的序列化特定属性。is
堆叠的圈复杂度。 - Stelios Adamantidis这使得静态类型语言(CLR)更容易与在动态语言运行时(DLR)上运行的动态语言(如Python、Ruby…)进行交互,参见MSDN:
例如,您可以使用以下代码来在C#中对XML中的计数器进行递增。
Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);
使用DLR,您可以使用以下代码执行相同的操作。
scriptobj.Count += 1;
MSDN列举了以下优点:
- 简化将动态语言移植到.NET Framework
- 在静态类型语言中启用动态特性
- 提供 DLR 和 .NET Framework 的未来优势
- 启用库和对象的共享
- 提供快速的动态分派和调用
更多细节请参见MSDN。
一个使用示例:
您使用了许多具有共同属性“CreationDate”的类:
public class Contact
{
// some properties
public DateTime CreationDate { get; set; }
}
public class Company
{
// some properties
public DateTime CreationDate { get; set; }
}
public class Opportunity
{
// some properties
public DateTime CreationDate { get; set; }
}
static DateTime RetrieveValueOfCreationDate(Object item)
{
return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
}
static DateTime RetrieveValueOfCreationDate(dynamic item)
{
return item.CreationDate;
}
dynamic
类型的变量来编写基于ADO.NET的数据访问层,使用SQLDataReader
调用现有的遗留存储过程时,发现它是最理想的应用情况。这些遗留存储过程包含大量业务逻辑,我的数据访问层需要返回某种结构化数据给基于C#的业务逻辑层进行一些操作(尽管几乎没有)。每个存储过程返回不同的数据集(表列)。因此,我编写了下面的代码,看起来相当优雅和简洁,而不是创建数十个类或结构体来保存返回的数据并将其传递给BLL层。public static dynamic GetSomeData(ParameterDTO dto)
{
dynamic result = null;
string SPName = "a_legacy_stored_procedure";
using (SqlConnection connection = new SqlConnection("my connection string"))
{
SqlCommand command = new SqlCommand(SPName, connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("@empid", dto.EmpID));
command.Parameters.Add(new SqlParameter("@deptid", dto.DeptID));
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
dynamic row = new ExpandoObject();
row.EmpName = reader["EmpFullName"].ToString();
row.DeptName = reader["DeptName"].ToString();
row.AnotherColumn = reader["AnotherColumn"].ToString();
result = row;
}
}
}
return result;
}
COM互操作。特别是IUnknown接口。它是专门为此设计的。
dynamic
类型的案例是针对具有协变或逆变问题的虚拟方法。其中一个例子是臭名昭著的 Clone
方法,它返回与调用它的对象相同类型的对象。这个问题不能完全通过动态返回解决,因为它绕过了静态类型检查,但至少你不需要像使用普通的 object
时那样经常使用丑陋的转换。换句话说,强制转换变得隐式化了。public class A
{
// attributes and constructor here
public virtual dynamic Clone()
{
var clone = new A();
// Do more cloning stuff here
return clone;
}
}
public class B : A
{
// more attributes and constructor here
public override dynamic Clone()
{
var clone = new B();
// Do more cloning stuff here
return clone;
}
}
public class Program
{
public static void Main()
{
A a = new A().Clone(); // No cast needed here
B b = new B().Clone(); // and here
// do more stuff with a and b
}
}
dynamic np = Py.Import("numpy")
dynamic
。这提供了类型安全性并避免了泛型的限制。本质上这是*鸭子类型:T y = x * (dynamic)x
,其中typeof(x) is T
dynamic i = 12;
i = "text";
因此,您可以根据需要更改类型。将其用作最后的手段;它是有益的,但我听说在生成的IL方面会发生很多事情,这可能会导致性能问题。