为什么当将动态项目传递给方法时,编译器不会验证方法的返回类型?

huangapple go评论70阅读模式
英文:

Why a dynamic item, when passed to a method, the method's return type is not validated by the compiler?

问题

在C#中,使用for循环返回动态item时,当将item传递到方法中时,如果将结果分配给不兼容类型的变量,编译器不会验证方法的返回类型。

为什么编译器不会引发类型转换错误,在下面的示例中有更好的解释:

void Main()
{
   List<dynamic> vals = new List<dynamic>(100);
   for (int i = 0; i < 100; i++) vals.Add(new { Id = i});
   
   foreach (dynamic itm in vals)
   {
      //没有编译错误,为什么??结果应该是int,因为GetId返回int!
      string result = GetId(itm);
      Console.WriteLine($"id: {result}");     
   }

   //id2必须是int,否则编译错误
   int id2 = GetId(new {Id = 100});
}

public static int GetId(dynamic b)
{
   return b.Id;
}

上述代码能够编译通过,但会在运行时引发错误,这是预期的运行时错误。

> HResult -2146233088
InnerException null
Message 无法隐式地将类型'int'转换为'string'。

英文:

In C#, in a for loop returning a dynamic item, when the item is passed into a method, the method's return type is not validated by compiler, when assigning the result to an incompatible typed variable.

NOTE: There is a similar question but that relates to changing the return type, whereas here we focus on compiler type checking, an important difference on Google searches.

Better explained in the example below, why is the compiler not raising a type conversion error?

void Main()
{
   List&lt;dynamic&gt; vals = new List&lt;dynamic&gt;(100);
   for (int i = 0; i &lt; 100; i++) vals.Add(new { Id = i});
   
   foreach (dynamic itm in vals)
   {
      //no compile error, why?? result should be an int, as GetId returns an int!
      string result = GetId(itm);
      Console.WriteLine($&quot;id: {result}&quot;);     
   }

   //id2 must be an int, else compile error
   int id2 = GetId(new {Id = 100});
}

public static int GetId(dynamic b)
{
   return b.Id;
}

The above compiles, but will result in a runtime error, naturally this runtime error is expected.

> HResult -2146233088
InnerException null
Message Cannot implicitly convert type 'int' to 'string'

答案1

得分: 7

C#标准的11.3.1节

> 但是,如果一个表达式是动态表达式(即具有dynamic类型),这表示它参与的任何绑定都应该基于它的运行时类型,而不是它在编译时的类型。因此,此类操作的绑定被推迟到程序运行时执行该操作的时间。这被称为动态绑定

其中的“它参与的任何绑定”意味着,每当你使用动态参数调用一个方法(或者在dynamic类型的表达式上调用方法),该调用的结果也是动态的。

编译器将验证是否有一个可以接受你提供的参数数量的GetId方法,但它不会进行其他的重载解析 - 这意味着它不会尝试确定可能的返回类型集。

例如,假设你的实际代码是:

public static int GetId(int b) => b;
public static string GetId(string c) => c;

...你的调用代码仍然会编译通过,在执行时会选择正确的重载,但编译器无法告诉你是否会得到一个字符串还是一个整数。

与其在简单的情况下提供更多信息,语言规则基本上是这样说的:“不,你现在处于动态世界 - 我们不会做一半的事情。”

英文:

From section 11.3.1 of the C# standard:

> However, if an expression is a dynamic expression (i.e., has the type dynamic) this indicates that any binding that it participates in should be based on its run-time type rather than the type it has at compile-time. The binding of such an operation is therefore deferred until the time where the operation is to be executed during the running of the program. This is referred to as dynamic binding.

That "any binding that it participates in" means that any time you call a method with a dynamic argument (or on an expression of type dynamic), the result of that call is also dynamic.

The compiler will validate that there's a GetId method which can be called with the number of arguments you're providing, but it doesn't do any overload resolution beyond that - which means it doesn't try to work out the potential set of return types.

For example, suppose your actual code was:

public static int GetId(int b) =&gt; b;
public static string GetId(string c) =&gt; c;

... your calling code would still compile, and the right overload would be picked at execution time, but the compiler can't tell you whether you'll get a string or an int back.

Rather than trying to provide more information in the simpler case, the language rules basically say "Nope, you're in a dynamic world now - we're not going to go half way."

答案2

得分: 1

dynamic类型是一种静态类型,但是dynamic类型的对象绕过了静态类型检查。在大多数情况下,它的行为类似于object类型。编译器不会发出警告,因为它假定dynamic元素支持任何操作。

英文:

The dynamic type is a static type, but an object of type dynamic bypasses static type checking. In most cases, it functions like it has type object. The compiler doesn't give you a warning because it assumes a dynamic element supports any operation.

答案3

得分: 1

dynamic 是完全在运行时进行的事情。仅会执行最少的编译时检查(例如,所需名称的方法存在以及可以使用所需数量的参数进行调用(在本例中为 1 个参数))。编译器甚至不会生成一个真正的函数调用,它会生成指令,这些指令将根据动态变量的实际内容在运行时创建真正的调用代码。因此,你的 string text = GetId(itm); 编译成类似以下的内容:

if (<>o__0.<>p__1 == null)
{
    <>o__0.<>p__1 = CallSite<Func<CallSite, object, string>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.Convert(CSharpBinderFlags.None, typeof(string), typeof(C)));
}
Func<CallSite, object, string> target = <>o__0.<>p__1.Target;
CallSite<Func<CallSite, object, string>> <>p__ = <>o__0.<>p__1;
if (<>o__0.<>p__0 == null)
{
    Type typeFromHandle = typeof(C);
    CSharpArgumentInfo[] array = new CSharpArgumentInfo[2];
    array[0] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null);
    array[1] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);
    <>o__0.<>p__0 = CallSite<Func<CallSite, C, object, object>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(CSharpBinderFlags.InvokeSimpleName, "GetId", null, typeFromHandle, array));
}
string text = target(<>p__, <>o__0.<>p__0.Target(<>o__0.<>p__0, this, arg));

在上面的代码中:

  • C 是你的类类型(this
  • argitm

因此,在这段代码中,编译时根本不知道任何类型。它只在运行时得到完全解析。

最后,上述都是实现细节。语言标准(11.3.1 "General")规定了以下内容:

但是,如果表达式是动态表达式(即,具有 dynamic 类型),这表明它参与的任何绑定应基于其运行时类型,而不是它在编译时的类型。因此,这种操作的绑定被推迟到程序运行时要执行该操作的时间。这被称为动态绑定。

当操作进行动态绑定时,编译器几乎不执行任何检查。相反,如果运行时绑定失败,错误将作为运行时异常报告。

英文:

dynamic is fully run-time thing by its nature. Only minimal compile-time checks are performed (e.g. the method with desired name exists and and it can be called with desired number of arguments (1 argument in this case)). Compiler even does not generate a real function call here, it generates instructions which will create a real calling code in run-time depending on real contents of dynamic variable. So, your string text = GetId(itm); compiles into something like this

if (&lt;&gt;o__0.&lt;&gt;p__1 == null)
{
    &lt;&gt;o__0.&lt;&gt;p__1 = CallSite&lt;Func&lt;CallSite, object, string&gt;&gt;.Create(Microsoft.CSharp.RuntimeBinder.Binder.Convert(CSharpBinderFlags.None, typeof(string), typeof(C)));
}
Func&lt;CallSite, object, string&gt; target = &lt;&gt;o__0.&lt;&gt;p__1.Target;
CallSite&lt;Func&lt;CallSite, object, string&gt;&gt; &lt;&gt;p__ = &lt;&gt;o__0.&lt;&gt;p__1;
if (&lt;&gt;o__0.&lt;&gt;p__0 == null)
{
    Type typeFromHandle = typeof(C);
    CSharpArgumentInfo[] array = new CSharpArgumentInfo[2];
    array[0] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null);
    array[1] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);
    &lt;&gt;o__0.&lt;&gt;p__0 = CallSite&lt;Func&lt;CallSite, C, object, object&gt;&gt;.Create(Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(CSharpBinderFlags.InvokeSimpleName, &quot;GetId&quot;, null, typeFromHandle, array));
}
string text = target(&lt;&gt;p__, &lt;&gt;o__0.&lt;&gt;p__0.Target(&lt;&gt;o__0.&lt;&gt;p__0, this, arg));

In code above

  • C is your class type (this)
  • arg is itm

So, in this code there is no types known in compile-time at all. It will be fully resolved only in compile-time.

And finally, all above were implementation details. And the language standard (11.3.1 "General") says the following

> However, if an expression is a dynamic expression (i.e., has the type
> dynamic) this indicates that any binding that it participates in
> should be based on its run-time type rather than the type it has at
> compile-time. The binding of such an operation is therefore deferred
> until the time where the operation is to be executed during the
> running of the program. This is referred to as dynamic binding.
>
> When an operation is dynamically bound, little or no checking is
> performed by the compiler
. Instead if the run-time binding fails,
> errors are reported as exceptions at run-time.

huangapple
  • 本文由 发表于 2023年3月10日 00:00:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/75687111.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定