C#4.0引入了一种名为“动态”的新类型。这听起来不错,但是程序员会用它做什么?
是否有可以挽救这一天的情况?
答案 0 :(得分:193)
添加了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变得更容易使用:
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#编译器现在将注入必要的代码,并在运行时使用新类,几乎完成与之前完全相同的操作,但语法已经隐藏了,现在你可以专注于什么,而不是如何。 Anders Hejlsberg喜欢说你必须引用不同的“咒语”,这是对整个事物的魔力的一种双关语,你通常需要挥动你的手并按照正确的顺序说出一些神奇的词语获得某种类型的咒语。与COM对象交谈的旧API方式很多,你需要跳过很多箍,以便哄骗编译器为你编译代码。
如果你试图与一个没有接口或类的COM对象交谈,那么在版本4.0之前的C#中就会出现问题,你所拥有的只是IDispatch
引用。
如果您不知道它是什么,IDispatch
基本上是COM对象的反射。使用IDispatch
接口,您可以询问对象“称为Save的方法的id号是什么”,并构建包含参数值的特定类型的数组,最后调用Invoke
方法在IDispatch
界面上调用方法,将您设置的所有信息传递到一起。
上面的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运行时构建的任何其他语言。
如果您想了解更多相关信息,可以在Wikipedia: IDispatch上找到有关IDispatch
界面的更多信息。这真是血腥的东西。
但是,如果你想与Python对象交谈怎么办?与用于COM对象的API相比,它有一个不同的API,并且由于Python对象本质上也是动态的,因此您需要求助于反射魔法来找到正确的调用方法,它们的参数等,而不是.NET。反射,为Python编写的东西,与上面的IDispatch代码非常相似,只是完全不同。
对于Ruby?还有一个不同的API。
的JavaScript?同样的交易,不同的API。
动态关键字包含两件事:
dynamic
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 :(得分:161)
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属性。当然,现在,当您使用动态变量时,您放弃了编译器类型检查。这意味着调用cust.MissingMethod()将编译,直到运行时才会失败。此操作的结果是RuntimeBinderException,因为未在Customer类上定义MissingMethod。
上面的示例显示了调用方法和属性时动态的工作原理。另一个强大(并且具有潜在危险)的功能是能够为不同类型的数据重用变量。我确信那里的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.Sqrt需要一个double。显然,您所要做的就是强制转换和/或更改变量类型,但可能存在动态使用的情况。
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
答案 2 :(得分:21)
我很惊讶没有人提到multiple dispatch。解决此问题的常用方法是通过Visitor pattern,但这并非总是可行,因此您最终会进行堆叠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对象转换为可序列化版本时,该功能非常方便。我不想让访问者污染我的代码,我也不想用无用的序列化特定属性来污染我的ChartItem
对象。
答案 3 :(得分:11)
它使静态类型语言(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。
答案 4 :(得分:4)
使用示例:
你消耗了许多具有公共财产的类' 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;
}
答案 5 :(得分:3)
COM互操作。特别是IUnknown。它是专门为它设计的。
答案 6 :(得分:1)
它主要被RAD和Python受害者用来破坏代码质量,IntelliSense和编译时错误检测。
答案 7 :(得分:1)
'动态'的最佳用例对我来说,类型变量最近是在ADO.NET中编写数据访问层(使用SQLDataReader ),而代码是调用已编写的旧存储过程。有数百个遗留存储过程包含大量业务逻辑。我的数据访问层需要将某种结构化数据返回到业务逻辑层(基于C#),以进行一些操作(尽管几乎没有)。每个存储过程都返回不同的数据集(表列)。因此,我没有创建数十个类或结构来保存返回的数据并将其传递给BLL,而是编写了下面的代码,看起来非常优雅和整洁。
{{1}}
答案 8 :(得分:0)
dynamic np = Py.Import("numpy")
dynamic
。这提供了类型安全性并避免了泛型的限制。这实际上是*鸭子打字: T y = x * (dynamic)x
,其中typeof(x) is T
答案 9 :(得分:0)
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
}
}
答案 10 :(得分:-1)
它在运行时进行评估,因此您可以在JavaScript中将类型切换为您想要的任何类型。这是合法的:
dynamic i = 12;
i = "text";
因此您可以根据需要更改类型。用它作为最后的手段;它是有益的,但我听说在生成的IL方面有很多事情要做,并且可以达到性价格。