两个 C# 扩展泛型方法之间的不明确调用,其中一个 where T:class 和另一个 where T:struct

发布于 2024-09-29 02:45:33 字数 510 浏览 4 评论 0原文

考虑两个扩展方法:

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

和一个类:

class MyClass() { ... }

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

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

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

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:

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

云淡月浅 2024-10-06 02:45:33

编辑:我现在已经写了关于此的博客更详细地说。


我最初的(现在我认为不正确)想法:在重载解析和类型推断阶段不考虑通用约束 - 它们仅用于验证重载解析的结果。

编辑:好的,经过长时间的讨论后,我想我已经到了。基本上我的第一个想法几乎是正确的。

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

例如:

// 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(null),则上述方法不会成为候选集的一部分,因为 Nullable; value 无法满足 Nullable 的约束。如果有其他适用的方法,调用仍然可以成功。

现在,在上面的情况下,约束是完全相同的......但它们不必如此。例如,请考虑:

class Factory<TItem> where TItem : new()

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

如果您尝试调用 Foo(null),该方法仍将是候选集的一部分 - 因为当 TItemobjectFactory 中表达的约束仍然成立,并且是在构建候选集时检查的内容。如果这被证明是最好的方法,那么稍后在 7.6.5.1 的结束附近它就会失败:

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

Eric 的博客文章< /a> 包含更多详细信息。

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.

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:

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.

Eric's blog post contains more detail on this.

别念他 2024-10-06 02:45:33

埃里克·利珀特 (Eric Lippert) 的解释比我更好,此处

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

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)

Eric Lippert explains better than I ever could, here.

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)
玻璃人 2024-10-06 02:45:33

我发现这种“有趣”的奇怪方法在 .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();
    }
}

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 :

/// <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();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文