为什么将null转换为方法选择的类型有效?

本文关键字:类型 有效 选择 方法 null 转换 为什么 | 更新日期: 2023-09-27 18:06:02

根据c#的答案:将null传递给重载方法-调用哪个方法?,空值似乎携带类型信息。实际上,我也可以使用

class Program
{
    static void Method(TypeA a)
    {
        Console.Write("Output of Method(TypeA a):");
        Console.WriteLine(a); // Prints nothing, on account of the null.
    }
    static void Method(TypeB b)
    {
        Console.Write("Output of Method(TypeB b):");
        Console.WriteLine(b); // Also prints nothing, on account of the null.
    }
    static void Main()
    {
        var a = (TypeA)null;
        var b = (TypeB)null;
        Method(a);
        Method(b);
    }
}
class TypeA { }
class TypeB { }
收益率

方法(TypeA a)输出:
方法(TypeB b)输出:

这是怎么回事?

为什么将null转换为方法选择的类型有效?

不,null本身不携带类型信息。强制转换只是告诉编译器变量ab应该是什么类型…如果没有强制类型转换,它无法判断,因为null可以转换为任何引用类型或可空类型。

然后在重载解析中使用这些变量的类型。不要忘记,这里的选择只是在编译时做出的——它根本不涉及实参的执行时值。

你的代码正好相当于:

TypeA a = null;
TypeB b = null;
Method(a);
Method(b);

如果您使用动态类型,以便在执行时执行重载解析,您将得到一个失败:

dynamic foo = (TypeA) null; // Or without the cast. It makes no difference.
Method(foo); // RuntimeBinderException at execution time, due to ambiguity

虽然所呈现的情况很明显,但我偶然发现了一些涉及动态强制转换的奇怪行为。看看下面的程序:

class Base { }
class Child : Base { }
class Program
{
    static void Main(string[] args)
    {
        Base node = GetChild();
        Test((dynamic) node);
        node = GetBase();
        Test((dynamic) node);
    }
    static Child GetChild()
    {
        return null;
    }
    static Base GetBase()
    {
        return null;
    }
    // Guess how many times each method is called..
    static void Test(Base node)
    {
        // Nope!
    }
    static void Test(Child child)
    {
        // It's this one twice.
    }
}

用。net反射器检查代码会导致崩溃(歧义匹配),但是dotPeek提供了对生成的IL(在这里反编译)的更仔细的查看:

private static void Main(string[] args)
{
  Base base1 = (Base) Program.GetChild();
  if (Program.<Main>o__SiteContainer0.<>p__Site1 == null)
    Program.<Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Test", (IEnumerable<Type>) null, typeof (Program), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[2]
    {
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, (string) null),
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null)
    }));
  Program.<Main>o__SiteContainer0.<>p__Site1.Target((CallSite) Program.<Main>o__SiteContainer0.<>p__Site1, typeof (Program), (object) base1);
  Base base2 = Program.GetBase();
  if (Program.<Main>o__SiteContainer0.<>p__Site2 == null)
    Program.<Main>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Test", (IEnumerable<Type>) null, typeof (Program), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[2]
    {
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, (string) null),
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null)
    }));
  Program.<Main>o__SiteContainer0.<>p__Site2.Target((CallSite) Program.<Main>o__SiteContainer0.<>p__Site2, typeof (Program), (object) base2);
}

我有点理解动态调用站点的构建,并且使用两个变量(base1和base2)而源只使用一个变量的事实可能是由于优化。但是,base2变量与Child类型没有任何关系!它被声明为Base类型的变量,并从具有Base原型的函数中初始化为null。

添加另一个类和处理程序方法可以通过抛出运行时异常来实现您可能期望或不期望的功能,"调用在以下方法或属性之间是不明确的:'Program.Test(Child)'和'Program.Test(AnotherChild)'"。此异常在第一次和第二次动态强制转换时都抛出,这是意料之中的,因为它们是类似的代码。

class AnotherChild : Base { }
static void Test(AnotherChild child)
{
    // Ambiguous between AnotherChild and Child -> Runtime Exception
}

换句话说,无论类型如何,对null对象的动态强制转换都将自下而上地遍历继承树,当在该级别上发现多个类型时放弃。与上面一样,让AnotherChild从Child继承确实会调用AnotherChild处理程序。

class AnotherChild : Child { }

老实说,我认为在null对象上做动态强制转换是非法的,如果每次都抛出的话会好得多。当你在做动态强制转换时,你可能还会使用方法重载;

当您编写var时,编译器根据您为其分配的内容确定变量的类型。

通过显式的(C-Style)强制转换,你在说"这就是这个类型是什么",var拾取它,导致重载工作如所示。

赋值"null"对于任何引用类型都是有效的(因此当然可以编译),但是类型信息是由强制转换提供的。

空值似乎携带类型信息

不,变量有一个类型。null就是null调用哪个方法是在编译时确定的。当你这样做时:

var a = (TypeA)null;     
var b = (TypeB)null;
Method(a);
Method(b);

编译器 Method(a)绑定到Method(TypeA),因为您在调用中使用了TypeA类型的变量。Method(b)也是如此。您引用的问题更详细地解释了绑定。

要证明null不携带类型信息,添加第三个调用,将方法绑定延迟到运行时:

static void Main()
{
    var a = (TypeA)null;
    var b = (TypeB)null;
    dynamic c = a;
    Method(a);
    Method(b);
    Method(c);   // will throw a `RuntimeBinderException` since the type of c can't be determined at run-time.
}
相关文章: