什么是动态的'类型在c# 4.0用于?

c# 4.0引入了一种叫做“动态”的新类型。这听起来不错,但是程序员要用它来做什么呢?

在某种情况下,它是否可以挽救局面?

179838 次浏览

COM互操作。尤其是IUnknown。这是专门为它设计的。

它在运行时求值,所以你可以像在JavaScript中那样切换类型到你想要的任何类型。这是正确的:

dynamic i = 12;
i = "text";

所以你可以根据需要改变类型。把它作为最后的手段;这是有益的,但我听说在生成IL的场景下发生了很多事情,这可能以性能代价为代价。

dynamic关键字是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属性。当然,当你使用动态变量时,你就放弃了编译器类型检查。这意味着调用cast . missingmethod()将被编译,直到运行时才会失败。这个操作的结果是一个RuntimeBinderException,因为MissingMethod没有在Customer类上定义。

上面的例子展示了动态在调用方法和属性时是如何工作的。另一个强大的(潜在的危险)特性是能够为不同类型的数据重用变量。我相信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类型,第3行不能编译,因为Math。根特期望双倍。显然,您所要做的就是强制转换和/或更改变量类型,但可能在某些情况下使用动态是有意义的。

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

它使静态类型语言(CLR)更容易与运行在DLR(动态语言运行库)上的动态类型语言(python, ruby…)互操作,参见MSDN:

例如,您可以使用以下代码来递增一个计数器

Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);

通过使用DLR,您可以使用以下代码代替 相同操作。< / p >

scriptobj.Count += 1;

MSDN列出了以下优点:

  • 简化将动态语言移植到。net框架
  • 在静态类型语言中启用动态特性
  • 提供DLR和.NET框架的未来好处
  • 启用库和对象的共享
  • 提供快速动态分派和调用

更多细节参见MSDN

dynamic关键字与c# 4.0的许多其他新特性一起被添加,以简化与其他运行时中或来自其他运行时的具有不同api的代码的对话。

举个例子。

如果你有一个COM对象,比如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之前没有可选参数的概念。在c# 4.0中,COM api通过引入:

  1. 可选参数
  2. 使ref对于COM api是可选的
  3. 命名参数

上述调用的新语法将是:

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#编译器现在将注入必要的代码,并在运行时中与新类一起工作,以完成几乎与您以前所做的完全相同的事情,但语法已对您隐藏,现在您可以专注于什么,而不是如何。安德斯·海尔斯伯格(Anders Hejlsberg)喜欢说,你必须调用不同的“咒语”,这是对整个事情的魔法的一种双关,你通常必须挥舞你的手,并以正确的顺序说出一些魔法词,以获得某种类型的咒语。旧的API与COM对象对话的方式有很多,你需要跳过很多圈才能说服编译器为你编译代码。

在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);

基本上就是c#在表现力方面赶上了VB,但是用了正确的方式,通过使它可扩展,而不仅仅是COM。当然,这在VB中也是可用的。NET或任何其他构建在.NET运行时之上的语言。

如果你想阅读更多关于IDispatch接口的信息,你可以在维基百科:IDispatch上找到它。这真的很血腥。

但是,如果您想与Python对象对话呢?有一个不同于用于COM对象的API,因为Python对象本质上也是动态的,你需要求助于反射魔法来找到要调用的正确方法,它们的参数等等,但不是。net反射,一些为Python编写的东西,很像上面的IDispatch代码,只是完全不同。

鲁比呢?仍然是不同的API。

JavaScript ?同样的操作,不同的API。

dynamic关键字由两部分组成:

  1. c#中的new关键字,dynamic
  2. 一组运行时类,它们知道如何处理不同类型的对象,实现dynamic关键字所需的特定API,并将调用映射到正确的做事方式。API甚至有文档记录,因此如果您有来自运行时的对象没有涉及到,您可以添加它。

然而,dynamic关键字并不意味着要取代任何现有的. net代码。当然,你可以这样做,但它并没有因为这个原因而被添加,而以Anders Hejlsberg为代表的c#编程语言的作者们一直非常坚定地认为,他们仍然认为c#是一种强类型语言,并且不会牺牲这一原则。

这意味着尽管你可以写这样的代码:

dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;

让它编译,它并不是一种“让我们在运行时弄清楚你想要什么”类型的系统。

整个目的是使与其他类型的对象的对话变得更容易。

互联网上有很多关于关键字的材料,支持者,反对者,讨论,咆哮,赞扬等等。

我建议你从以下链接开始,然后谷歌了解更多:

  1. 你可以使用pythonnet调用动态语言,比如CPython:

dynamic np = Py.Import("numpy")

  1. 在对泛型应用数值运算符时,可以将泛型强制转换为dynamic。这提供了类型安全性并避免了泛型的限制。这本质上是*鸭子打字:

T y = x * (dynamic)x,其中typeof(x) is T

我很惊讶没有人提到多分派。通常解决这个问题的方法是通过访问者模式,但这并不总是可行的,所以你最终会得到堆积的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);
}

注意,在第一种情况下,ElevationPointMapPoint的子类,如果它没有被放置之前 MapPoint,它将永远不会被到达。dynamic则不是这样,因为最接近的匹配方法将被调用。

正如您可能从代码中猜到的那样,当我执行从ChartItem对象到其可序列化版本的转换时,该特性非常方便。我不想用访问者污染我的代码,我也不想用无用的序列化特定属性污染我的ChartItem对象。

用法示例:

你使用了许多具有共同属性'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; }
    

}

如果你写一个通用的方法来检索'CreationDate'属性的值,你必须使用反射:

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;
}

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
}
}