两个C#扩展泛型方法之间的模糊调用,其中T:class和其他T:struct

时间:2010-10-25 11:19:33

标签: c# .net extension-methods generics

考虑两种扩展方法:

public static T MyExtension<T>(this T o) where T:class
public static T MyExtension<T>(this T o) where T:struct

一堂课:

class MyClass() { ... }

现在在上述类的实例上调用extension方法:

var o = new MyClass(...);
o.MyExtension(); //compiler error here..
o.MyExtension<MyClass>(); //tried this as well - still compiler error..

编译器说当我在类上调用它时调用该方法是一个模糊的调用。我原以为它可以确定调用哪个扩展方法,因为MyClass是一个类,而不是一个结构?

3 个答案:

答案 0 :(得分:36)

编辑:我现在更详细地blogged about this了。


我原来的(我现在认为不正确)认为:在重载决策和类型推断阶段不考虑泛型约束 - 它们仅用于验证重载决策的结果。

编辑:好的,在经历了很多之后,我想我就在那里。基本上我的第一个想法是几乎正确。

通用类型约束仅用于从非常有限的环境中的候选集中删除方法...特别是,仅当参数本身的类型是通用的时;不只是一个类型参数,而是使用泛型类型参数的泛型类型。此时,它是对已验证的泛型类型的类型参数的约束,而不是您正在调用的泛型方法的类型参数的约束。

例如:

// Constraint won't be considered when building the candidate set
void Foo<T>(T value) where T : struct

// The constraint *we express* won't be considered when building the candidate
// set, but then constraint on Nullable<T> will
void Foo<T>(Nullable<T> value) where T : struct

因此,如果您尝试调用Foo<object>(null)上述方法将不会成为候选集的一部分,因为Nullable<object> value无法满足Nullable<T>的约束}。如果有任何其他适用的方法,则呼叫仍然可以成功。

现在在上面的例子中,约束完全相同......但它们不一定是。例如,考虑:

class Factory<TItem> where TItem : new()

void Foo<T>(Factory<T> factory) where T : struct

如果您尝试拨打Foo<object>(null),该方法仍将是候选集的一部分 - 因为当TItemobject时,Factory<TItem>中表达的约束仍然有效,和那是在构建候选集时检查了什么。如果这是最好的方法,那么它稍后将在7.6.5.1的 end 附近失败验证:

  

如果最佳方法是泛型方法,则根据泛型方法上声明的约束(第4.4.4节)检查类型参数(提供或推断)。如果任何类型参数不满足类型参数的相应约束,则会发生绑定时错误。

Eric blog post包含更多详细信息。

答案 1 :(得分:10)

Eric Lippert比我更好地解释,here

我自己也遇到过这种情况。我的解决方案是

public void DoSomthing<T> (T theThing){
    if (typeof (T).IsValueType)
        DoSomthingWithStruct (theThing);
    else
        DoSomthingWithClass (theThing);  
}

// edit - seems I just lived with boxing

public void DoSomthingWithStruct (object theThing)
public void DoSomthingWithClass(object theThing)

答案 2 :(得分:4)

我发现这种“有趣”奇怪的方式在.NET 4.5中使用默认参数值:)可能对于教育\推测目的比实际使用更有用,但我想展示它:

/// <summary>Special magic class that can be used to differentiate generic extension methods.</summary>
public class MagicValueType<TBase>
    where TBase : struct
{
}

/// <summary>Special magic class that can be used to differentiate generic extension methods.</summary>
public class MagicRefType<TBase>
    where TBase : class
{
}

struct MyClass1
{
}

class MyClass2
{
}

// Extensions
public static class Extensions
{
    // Rainbows and pink unicorns happens here.
    public static T Test<T>(this T t, MagicRefType<T> x = null)
        where T : class
    {
        Console.Write("1:" + t.ToString() + " ");
        return t;
    }

    // More magic, other pink unicorns and rainbows.
    public static T Test<T>(this T t, MagicValueType<T> x = null)
        where T : struct
    {
        Console.Write("2:" + t.ToString() + " ");
        return t;
    }
}

class Program
{
    static void Main(string[] args)
    {

        MyClass1 t1 = new MyClass1();
        MyClass2 t2 = new MyClass2();

        MyClass1 t1result = t1.Test();
        Console.WriteLine(t1result.ToString());

        MyClass2 t2result = t2.Test();
        Console.WriteLine(t2result.ToString());

        Console.ReadLine();
    }
}