1

根据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):

这里发生了什么?

4

4 回答 4

3

不,null 本身不携带类型信息。强制转换只是告诉编译器变量的类型a应该b是什么......没有强制转换就无法判断,因为它可以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
于 2014-05-29T20:39:28.293 回答
1

虽然提出的案例很明显,但我偶然发现了一些涉及动态转换的奇怪行为。看看下面的程序:

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
}

换句话说,似乎无论类型如何,对空对象的动态转换都会自下而上遍历继承树,并在找到该级别上的多个类型时放弃。作为上述构造,让 AnotherChild 继承自Child 确实调用了 AnotherChild 处理程序。

class AnotherChild : Child { }

老实说,我认为对空对象进行动态转换应该是非法的,如果每次都抛出会更好。当您进行动态转换时,您可能还会使用方法重载;这似乎是一个正在酝酿中的无声杀手。

于 2015-07-21T11:48:58.070 回答
0

当您编写var时,编译器会根据您分配的变量来确定变量的类型。

通过进行显式(C 样式)强制转换,您是在说“这就是这种类型”并var选择它,从而导致重载按显示的那样工作。

分配“null”对于任何引用类型都是一个有效值(所以当然可以编译),但类型信息由强制转换提供。

于 2014-05-29T20:39:15.837 回答
0

空值似乎带有类型信息

不,变量有一个类型。 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.
}
于 2014-05-29T20:40:45.513 回答