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

时间:2021-03-28 20:53:36

Consider two extension methods:

考虑两种扩展方法:

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

And a class:

一堂课:

class MyClass() { ... }

Now call the extension method on a instance of the above class:

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

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

The compiler says that calling the method is an ambiguous call when I call it on a class. I would have thought that it could determine which extension method to call, as MyClass is a class, not a struct?

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

3 个解决方案

#1


36  

EDIT: I've now blogged about this in more detail.

编辑:我现在更详细地在博客上写这篇文章。


My original (and I now believe incorrect) thought: generic constraints aren't taken into account during the overload resolution and type inference phases - they're only used to validate the result of the overload resolution.

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

EDIT: Okay, after a lot of going round on this, I think I'm there. Basically my first thought was almost correct.

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

Generic type constraints only act to remove methods from a candidate set in a very limited set of circumstances... in particular, only when the type of a parameter itself is generic; not just a type parameter, but a generic type which uses a generic type parameter. At that point, it's the constraints on the type parameters of the generic type which are validated, not the constraints on the type parameters of the generic method you're calling.

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

For example:

例如:

// 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

So if you try to call Foo<object>(null) the above method won't be part of the candidate set, because Nullable<object> value fails to satisfy the constraints of Nullable<T>. If there are any other applicable methods, the call could still succeed.

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

Now in the case above, the constraints are exactly the same... but they needn't be. For example, consider:

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

class Factory<TItem> where TItem : new()

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

If you try to call Foo<object>(null), the method will still be part of the candidate set - because when TItem is object, the constraint expressed in Factory<TItem> still holds, and that's what's checked when building up the candidate set. If this turns out to be the best method, it will then fail validation later, near the end of 7.6.5.1:

如果您尝试调用Foo (null),该方法仍将是候选集的一部分 - 因为当TItem是对象时,Factory 中表达的约束仍然成立,这就是构建候选项时所检查的内容组。如果这是最好的方法,那么它将在稍后的7.6.5.1结束时失败验证:

If the best method is a generic method, the type arguments (supplied or inferred) are checked against the constraints (§4.4.4) declared on the generic method. If any type argument does not satisfy the corresponding constraint(s) on the type parameter, a binding-time error occurs.

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

Eric's blog post contains more detail on this.

Eric的博客文章包含更多细节。

#2


10  

Eric Lippert explains better than I ever could, here.

Eric Lippert在这里解释得比我更好。

I have come across this myself. My solution was

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

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)

#3


4  

I found this "interesting" strange way to do that in .NET 4.5 using default parameter values :) Maybe is more useful for educational\speculative purposes than for real use but I would like to show it :

我在.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();
    }
}

#1


36  

EDIT: I've now blogged about this in more detail.

编辑:我现在更详细地在博客上写这篇文章。


My original (and I now believe incorrect) thought: generic constraints aren't taken into account during the overload resolution and type inference phases - they're only used to validate the result of the overload resolution.

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

EDIT: Okay, after a lot of going round on this, I think I'm there. Basically my first thought was almost correct.

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

Generic type constraints only act to remove methods from a candidate set in a very limited set of circumstances... in particular, only when the type of a parameter itself is generic; not just a type parameter, but a generic type which uses a generic type parameter. At that point, it's the constraints on the type parameters of the generic type which are validated, not the constraints on the type parameters of the generic method you're calling.

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

For example:

例如:

// 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

So if you try to call Foo<object>(null) the above method won't be part of the candidate set, because Nullable<object> value fails to satisfy the constraints of Nullable<T>. If there are any other applicable methods, the call could still succeed.

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

Now in the case above, the constraints are exactly the same... but they needn't be. For example, consider:

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

class Factory<TItem> where TItem : new()

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

If you try to call Foo<object>(null), the method will still be part of the candidate set - because when TItem is object, the constraint expressed in Factory<TItem> still holds, and that's what's checked when building up the candidate set. If this turns out to be the best method, it will then fail validation later, near the end of 7.6.5.1:

如果您尝试调用Foo (null),该方法仍将是候选集的一部分 - 因为当TItem是对象时,Factory 中表达的约束仍然成立,这就是构建候选项时所检查的内容组。如果这是最好的方法,那么它将在稍后的7.6.5.1结束时失败验证:

If the best method is a generic method, the type arguments (supplied or inferred) are checked against the constraints (§4.4.4) declared on the generic method. If any type argument does not satisfy the corresponding constraint(s) on the type parameter, a binding-time error occurs.

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

Eric's blog post contains more detail on this.

Eric的博客文章包含更多细节。

#2


10  

Eric Lippert explains better than I ever could, here.

Eric Lippert在这里解释得比我更好。

I have come across this myself. My solution was

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

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)

#3


4  

I found this "interesting" strange way to do that in .NET 4.5 using default parameter values :) Maybe is more useful for educational\speculative purposes than for real use but I would like to show it :

我在.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();
    }
}