C#4.0引入了一种称为“动态”的新类型。听起来不错,但是程序员会用它做什么呢?
是否有可以节省一天的情况?
C#4.0引入了一种称为“动态”的新类型。听起来不错,但是程序员会用它做什么呢?
是否有可以节省一天的情况?
Answers:
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键入为双精度,而第3行不编译,因为Math.Sqrt需要双精度。显然,您所需要做的就是强制转换和/或更改变量类型,但是在某些情况下,可以使用动态变量。
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
in c#解决可以通过标准c#功能和静态类型或者最多使用类型推断(var
)解决的问题(甚至更好)。dynamic
应该只有当它涉及到与DLR interoperabilty问题被使用。如果您使用静态类型的语言(例如c#是)编写代码,请执行该操作,而不要模拟动态语言。那太丑了。
dynamic
在不需要代码的地方大量使用了变量(例如在带有平方根的示例中),则放弃干净的编译时错误检查;相反,您现在遇到了可能的运行时错误。
在dynamic
加入关键字,用C#4.0的许多其他新功能一起,以使其更简单交谈代码的生命或来自其他运行时,有不同的API。
举个例子。
如果您有一个像该Word.Application
对象这样的COM对象,并且想要打开一个文档,那么执行该操作的方法将带有不少于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);
注意所有这些论点吗?您需要传递这些参数,因为4.0版之前的C#没有可选参数的概念。在C#4.0中,通过引入以下内容,使COM API的使用更加容易:
ref
COM API可选上述调用的新语法为:
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
接口,您可以询问对象“什么是保存方法的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运行时之上构建的任何其他语言也可以使用此功能。
如果您想阅读更多有关该IDispatch
界面的信息,请访问Wikipedia:IDispatch。这真的是血腥的东西。
但是,如果您想与Python对象对话怎么办?有一个与用于COM对象的API不同的API,并且由于Python对象本质上也是动态的,因此您需要借助反射魔术来找到正确的调用方法,它们的参数等,但.NET则不是。反射,是为Python编写的东西,与上面的IDispatch代码非常相似,只是完全不同。
还有露比吗 仍然使用其他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;
并进行编译,这并不意味着它可以让您在运行时知道什么类型的系统。
整个目的是使与其他类型的对象交谈变得更加容易。
互联网上有很多有关关键字,支持者,反对者,讨论,骂,称赞等的材料。
我建议您从以下链接开始,然后从谷歌获取更多信息:
dynamic
要添加它的原因,以支持其他生态系统如何完成类似于反射的方法调用,并为数据结构提供一种黑盒方法,并以书面形式实现此目的。
我很惊讶没有人提到多次派遣。解决此问题的通常方法是通过“ 访问者”模式,但这并非总是可能的,因此最终会堆积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
与无用的序列化特定属性一起污染我的对象。
is
一个令人恐惧的堆叠。
magic
; 没有魔术。
它使静态类型语言(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; }
}
如果编写用于检索“ CreationDate”属性值的公共方法,则必须使用反射:
static DateTime RetrieveValueOfCreationDate(Object item)
{
return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
}
借助“动态”概念,您的代码更加优雅:
static DateTime RetrieveValueOfCreationDate(dynamic item)
{
return item.CreationDate;
}
COM互操作。特别是IUnknown。它是专门为此设计的。
RAD和Python受害者将主要使用它来破坏代码质量,IntelliSense和编译时错误检测。
它在运行时求值,因此您可以像在JavaScript中一样将类型切换为所需的任何类型。这是合法的:
dynamic i = 12;
i = "text";
因此,您可以根据需要更改类型。不得已时使用它;这是有益的,但是我听说在幕后发生了很多有关生成IL的事情,而这可能是以性能为代价的。
对我来说,“动态”类型变量的最佳用例是,最近,当我在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(DataConnection.ConnectionString))
{
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 np = Py.Import("numpy")
dynamic
将数值运算符应用于泛型时,可以将泛型转换为泛型。这提供了类型安全性并避免了泛型的限制。这本质上是*鸭子键入:T y = x * (dynamic)x
,在哪里 typeof(x) is T
dynamic
键入的另一个用例是遇到协变量或协变量问题的虚拟方法。其中一个例子就是臭名昭著的Clone
方法,该方法返回与调用它的对象类型相同的对象。通过动态返回不能完全解决此问题,因为它绕过了静态类型检查,但至少您不需要像使用plain那样一直使用丑陋的强制转换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
}
}