别着了"类型推断"的道

自打.NET出了泛型之后,类型推断(Type Inference)就变得愈加强大。比如下面的代码:

 //常规泛型方法
var tuple1 = Tuple.Create<intstring>(2012"二零一二");
            
//泛型方法的自动类型推断(编译时)
var tuple2 = Tuple.Create(2012"二零一二");

以前需要定义泛型方法的参数类型,在基于编译时的类型推断系统的帮助下,立马简化为第二种写法。大大的提高了开发效率。

 

可是自动类型推断偶尔也会导致一些隐藏很深的bug出现。让我们先看一下下面的代码:

 class Foo
 {
     
public Foo()
     {
         var list 
= new List<string>();

         Execute(list);
     }

     
void Execute(IEnumerable<string> list)
     {
         Console.WriteLine(
"Execute(IEnumerable<string> list) invoked");
     }

     
void Execute<T>(T anotherArg)
     {
         Console.WriteLine(
"Execute<T>(T anotherArg) invoked");
     }
}

这个对象中,我们定义了两个方法,一个是常规方法,其要求传入一个类型为IEnumerable<string>的参数。另外一个则是一个泛型方法,其接受一个类型为T的参数。

在主方法体中,我们构造了一个List<string>,并调用Execute方法。我想你也许会期望其调用的是第一个方法。但是如果将这段代码运行后,会发现编译器在编译时,通过类型推断,自动帮你链接到了第二个泛型方法的调用上。这是因为编译器针对方法调用的绑定流程,是基于如下的优先级:

1)一致的方法签名(这里如果加上一个void Execute(List<string> list)方法,那么就会链接到此方法上)

2)类型推断

3)重载决策

 关于这一点,以前是自己实验出来,经过JimHappy#真嗨皮#郑海滨兄的指点,原来在C#语言规范中已经有详细的说明:

类型推断在方法调用的绑定时处理过程中进行,发生在调用的重载决策步骤之前。当在方法调用中指定了特定的方法组,并且没有在方法调用中指定类型实参时,将会对该方法组中的每个泛型方法应用类型推断。如果类型推断成功,则使用推断出的类型实参确定用于后续重载解析的实参的类型。如果重载决择选择一个泛型方法作为要调用的方法,则使用推断出的类型实参作为用于调用的实际类型实参。如果特定方法的类型推断失败,则该方法不参与重载决策。类型推断失败本身不会导致绑定时错误。但是,当重载决策未能找到任何适用的方法时,它通常会导致绑定时错误。

(摘自C#语言规范4.0,关于类型推断和重载决策的详细说明,大家可以参考“7.5.2 类型推断”以及“7.5.3 重载决策”这两个章节)

(C#语言规范4.0的下载地址:http://go.microsoft.com/fwlink/?LinkId=199552

 

上面就是类型推断流程在编译期对当前类调用方法的链接过程。那么针对基类方法,其处理过程又是如何?

    class Foo : Parent
    {
        
public Foo()
        {
            var list 
= new List<string>();

            Execute(list);
        }

        
void Execute(IEnumerable<string> list)
        {
            Console.WriteLine(
"Execute(IEnumerable<string> list) invoked");
        }

        
void Execute<T>(T anotherArg)
        {
            Console.WriteLine(
"Execute<T>(T anotherArg) invoked");
        }
    }

    
class Parent
    {
        
protected void Execute(List<string> list)
        {
            Console.WriteLine(
"Parent::Execute(List<string> list) invoked");
        }
    }

从刚才的执行优先级上我们可以知道,正对当前类,如果签名一致的话,则会调用此方法。在上面的代码中,基类定义了一个方法,其签名和子类的调用签名是一致的,但是当我们执行这段代码会发现,方法实际上链接到了子类的Execute<T>泛型方法体上。因此,上述的链接优先级,对基类方法不起作用。

当然,如果你将方法的调用前面加入base.Execute关键字,则一定会进入到基类的方法体中。

 

最后,说一下个人感觉:

1)类型推断是好东西,应该顶。不过要慎用,尤其是方法重载比较多的情况下。

2)编译器在方法链接的过程中,针对泛型方法其优先级还是比较高的,因此如果出现多个方法重载,且包括泛型的情况下,一定要多点小心。

3)如果自己明确的清楚自己要调用的方法是处于基类时,最后能够加入base关键字,以避免不必要的问题。当然,如果你已经对.net游刃有余,当然不必。

4)项目代码最终是要交给别人维护的,如果希望自己能早日甩掉这个摊子去玩些新东西,就最好能够写出让维护者一目了然的代码,否则永远甩不掉(扯远了 -_-!!)

 

这个账户注册5年了,就没好好写过一篇技术文章,惭愧。虽然所学有限,但还是希望可以将自己的一些心得分享出来,权当抛砖引玉。希望大家编码愉快!

posted @ 2011-08-22 22:41  杨二毛  阅读(2093)  评论(9编辑  收藏  举报