方法重载的通用约束

6

我有一个带有一些通用方法的接口,我想要实现一个可以重载的方法,以便接受一个类的实例或其PK值(它可以是int或GUID但会有所不同)。

我添加了两个类似于下面这些示例的方法:

    void DoSomething<TKey>(TKey key) where TKey: struct;
    void DoSomething<TModel>(TModel model) where TModel : class;

第二个中的“DoSomething”方法名被突出显示,错误是:
“类型'ISomeStuff'已经定义了一个具有相同参数类型的成员'DoSomething'。”
我感到惊讶,因为我明确地将参数定义为不同类型:一个是类,另一个是结构体。
为什么这不足以使签名不同呢?

1
可能是泛型约束,其中T:结构和T:类的重复。还请参阅Eric Lippert的文章此处 - Frédéric Hamidi
@Frederic:我怎么会错过那个!!! - Jon Egerton
显然,侧边栏中的“相关”窗格也没有选择它,所以可能比平常棘手一些 ;) - Frédéric Hamidi
啊,没错 - 我已经扫描了建议列表。 - Jon Egerton
4个回答

7
可以做到这一点,您需要创建类似于C++中的enable_if的东西。
public class ClassTag<V> where V : class { }

public class StructTag<V> where V : struct { }

public void Func<V>(V v, ClassTag<V> dummy = null) where V : class
{
    Console.Writeln("class");
}

public void Func<V>(V v, StructTag<V> dummy = null) where V : struct
{
    Console.Writeln("struct");
}

public void Func<V>(V? v, StructTag<V> dummy = null) where V : struct
{
    Console.Writeln("struct?");
}

static void Main()
{
    Func("A");
    Func(5);
    Func((int?)5);
}

它可以扩展使用任何不相交的 where 来区分重载。唯一的缺点是不能在另一个泛型方法内部使用:

public static void Z1<T>(T t) // where T : class
{
    Func(t); //error there
}

public static void Z2<T>(T t) where T : class
{
    Func(t); //ok 
}

编辑 但是在这种情况下,有可能使用 动态的 来解决这个限制:

public static void Z1<T>(T t)
{
     Func((dynamic)t); //if `T == int` it will call "struct" version
}

唯一的缺点是运行时间成本类似于对 Dictionary<,> 索引的调用。


我喜欢这个答案。 - John Paquin

4

Jon Skeet有关于一切问题的答案:点击我

引用:

声明仅在泛型约束方面不同,且约束不是签名的一部分


1
更新的链接,供未来读者使用(Jon在他的个人博客上发布了同一篇文章,链接在此): http://codeblog.jonskeet.uk/2010/10/28/overloading-and-generic-constraints/ - Arin

1
如果您不需要通用参数,只是想在编译时区分这些情况,您可以使用以下代码。
void Foo(object a) { } // reference type
void Foo<T>(T? a) where T : struct { } // nullable
void Foo(ValueType a) { } // value type

1
如果希望无论是否有类约束或结构体约束都可以泛型调用成员,并使其调用适当约束的方法,则可以定义一个接口IThingUser<T>来处理任何类型T,以及一个实现它的类用于值类型和另一个实现它的类用于类类型。有一个静态类ThingUsers<T>,其中包含一个类型为IThingUser<T>的静态字段TheUser,并将其填充为上述类之一的实例,然后ThingUsers<T>.theUser将能够处理任何类型的T
public static class GenTest93
{
    public interface IThingUser<T> { void ActOnThing(T it); }
    class StructUser<T> : IThingUser<T>, IThingUser<Nullable<T>> where T : struct
    {
        void IThingUser<T>.ActOnThing(T it) { System.Diagnostics.Debug.Print("Struct {0}", typeof(T)); }
        void IThingUser<Nullable<T>>.ActOnThing(T? it) { System.Diagnostics.Debug.Print("Struct? {0}", typeof(T)); }
    }
    class ClassUser<T> : IThingUser<T> where T : class
    {
        void IThingUser<T>.ActOnThing(T it) { System.Diagnostics.Debug.Print("Class {0}", typeof(T)); }
    }
    static class ThingUsers<T>
    {
        class DefaultUser : IThingUser<T>
        {
            public void ActOnThing(T it)
            {
                Type t = typeof(T);
                if (t.IsClass)
                    t = typeof(ClassUser<>).MakeGenericType(typeof(T));
                else
                {
                    if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
                        t = t.GetGenericArguments()[0];
                    t = typeof(StructUser<>).MakeGenericType(t);
                }
                TheUser = (IThingUser<T>)Activator.CreateInstance(t);
                TheUser.ActOnThing(it);
            }
        }
        static IThingUser<T> TheUser = new DefaultUser();
        public static void ActOnThing(T it) {TheUser.ActOnThing(it);}
    }
    public static void ActOnThing<T>(T it) { ThingUsers<T>.ActOnThing(it); }
    public static void Test()
    {
        int? foo = 3;
        ActOnThing(foo);
        ActOnThing(5);
        ActOnThing("George");
    }
}

如果编译器不知道T满足必要的约束条件,则需要使用反射来创建StructUser或ClassUser的实例,但这并不难。在第一次对特定T使用ActOnThing()之后,ThingUsers.TheUser将被设置为一个实例,可以直接用于任何未来对ActOnThing()的调用,因此性能应该非常好。
请注意,如果给定Nullable,则该方法会创建StructUser并将其转换为IThingUser>,而不是尝试创建sometype>,因为可空类型本身不满足任何约束。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接