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

两个C#扩展泛型方法之间的调用不明确,一个是where T:class,另一个是where T:struct,c#,.net,extension-methods,generics,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 现在对上述类的实例调用扩展方法: var o = new MyClass(...); o.MyExtension(); //compiler error here.. o.MyExtension<MyClass>();

考虑两种扩展方法:

public static T MyExtension<T>(this T o) where T:class
public static T MyExtension<T>(this T o) where T:struct
现在对上述类的实例调用扩展方法:

var o = new MyClass(...);
o.MyExtension(); //compiler error here..
o.MyExtension<MyClass>(); //tried this as well - still compiler error..
var o=newmyclass(…);
o、 MyExtension()//此处出现编译器错误。。
o、 MyExtension()//也尝试了此操作-仍然存在编译器错误。。
编译器说,当我在类上调用该方法时,调用该方法是一个不明确的调用。我原以为它可以决定调用哪个扩展方法,因为MyClass是一个类,而不是一个结构?

编辑:我现在已经详细介绍了


我最初的想法(现在我认为是不正确的):在重载解析和类型推断阶段没有考虑泛型约束——它们只用于验证重载解析的结果

编辑:好吧,经过很多讨论,我想我到了。基本上我的第一个想法几乎是正确的

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

例如:

// 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
//构建候选集时不考虑约束
void Foo(T值),其中T:struct
//在构建候选对象时,不会考虑约束*we express*
//设置,但可为null的约束将
void Foo(可空值),其中T:struct
因此,如果您尝试调用
Foo(null)
上述方法将不会成为候选集的一部分,因为
Nullable value
无法满足
Nullable
的约束。如果有任何其他适用的方法,调用仍然可以成功

现在在上面的例子中,约束是完全相同的。。。但他们不必如此。例如,考虑:

class Factory<TItem> where TItem : new()

void Foo<T>(Factory<T> factory) where T : struct
类工厂,其中TItem:new()
void Foo(工厂),其中T:struct
如果尝试调用
Foo(null)
,该方法仍将是候选集的一部分-因为当
TItem
object
时,
Factory
中表示的约束仍然有效,这是在构建候选集时检查的内容。如果这是最好的方法,那么在7.6.5.1的末尾,它将无法通过验证:

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


Eric's包含了更多的细节。

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)
public void DoSomthing(T theThing){
if(类型(T).IsValueType)
用结构(材料)进行施工;
其他的
与类(类)相关的内容;
}
//编辑-似乎我只是和拳击生活在一起
具有结构的公共无效文件(对象)
类(对象)中的公共void dosomthing
我发现在.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();
    }
}
///可用于区分泛型扩展方法的特殊magic类。
公共类MagicValueType
其中TBase:struct
{
}
///可以用来区分泛型扩展方法的特殊magic类。
公共类MagicRefType
在哪里?基地:班级
{
}
结构MyClass1
{
}
类别MyClass2
{
}
//扩展
公共静态类扩展
{
//彩虹和粉色独角兽在这里出现。
公共静态T测试(此T,MagicRefType x=null)
T:在哪里上课
{
Console.Write(“1:+t.ToString()+”);
返回t;
}
//更神奇的是,其他粉色独角兽和彩虹。
公共静态T测试(此T,MagicValueType x=null)
其中T:struct
{
Console.Write(“2:+t.ToString()+”);
返回t;
}
}
班级计划
{
静态void Main(字符串[]参数)
{
MyClass1 t1=新的MyClass1();
MyClass2 t2=新的MyClass2();
MyClass1 t1result=t1.Test();
Console.WriteLine(t1result.ToString());
MyClass2 t2result=t2.Test();
Console.WriteLine(t2result.ToString());
Console.ReadLine();
}
}

niat find!但你的问题是什么?解决办法?好问题。我以为我有一个简单的答案,但事实证明我没有。我希望你不介意我的“答案”与其说是答案本身,不如说是对正在发生的事情的探索。谢谢你的评论。Eamon-抱歉,我的问题不是很清楚-这就是为什么编译器不能确定要使用的最佳方法。在阅读了LukeH提供的评论和问题以及链接后,这是因为编译器在确定最佳使用方法时没有考虑类型约束。我已经编辑了我的答案。我想我现在明白发生了什么,但这很难解释…因为约束(和返回类型)不是方法签名的一部分,即使仅仅声明这两个方法也会给你带来编译错误(因为有相同的名称和完全相同的定义)。@Jon:我很确定你最初的答案是正确的,虽然我同意很难准确地看出规范中的具体位置/方式。第7.6.5.1节在这一点上看起来确实模棱两可,但约束不是签名的一部分这一观点已经得到了很好的证实(例如,知道这些事情的人自信地断言)。我对为什么您的示例没有编译的推测。。。7.5.3.2中的规则之一是
/// <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();
    }
}