通用约束,其中T:struct和T:class

时间:2010-06-04 13:23:30

标签: c# generics class struct constraints

我想区分以下情况:

  1. 普通值类型(例如int
  2. 可以为空的值类型(例如int?
  3. 参考类型(例如string) - 可选地,我不在乎这是否映射到上面的(1)或(2)
  4. 我提出了以下代码,适用于案例(1)和(2):

    static void Foo<T>(T a) where T : struct { } // 1
    
    static void Foo<T>(T? a) where T : struct { } // 2
    

    但是,如果我尝试像这样检测case(3),它就不会编译:

    static void Foo<T>(T a) where T : class { } // 3
    

    错误消息是类型'X'已经定义了一个名为'Foo'的成员,其参数类型相同。好吧,不知怎的,我无法区分where T : structwhere T : class

    如果删除第三个函数(3),则以下代码不会编译:

    int x = 1;
    int? y = 2;
    string z = "a";
    
    Foo (x); // OK, calls (1)
    Foo (y); // OK, calls (2)
    Foo (z); // error: the type 'string' must be a non-nullable value type ...
    

    如何让Foo(z)进行编译,将其映射到上述函数之一(或者是另一个具有另一个约束的函数,我没有想过)?

7 个答案:

答案 0 :(得分:48)

约束不是签名的一部分,但参数是。在重载解析期间强制执行参数约束。

因此,让我们将约束放在一个参数中。这很难看,但它确实有效。

class RequireStruct<T> where T : struct { }
class RequireClass<T> where T : class { }

static void Foo<T>(T a, RequireStruct<T> ignore = null) where T : struct { } // 1
static void Foo<T>(T? a) where T : struct { } // 2
static void Foo<T>(T a, RequireClass<T> ignore = null) where T : class { } // 3

(比从来没有晚了六年?)

答案 1 :(得分:19)

不幸的是,您无法仅根据约束来区分要调用的方法类型。

因此,您需要在不同的类中定义方法或使用不同的名称来定义方法。

答案 2 :(得分:10)

继续对Marnix's answer发表评论后,您可以通过一些反思来实现您的目标。

在下面的示例中,无约束Foo<T>方法使用反射来调用对相应约束方法的调用 - FooWithStruct<T>FooWithClass<T>。出于性能原因,我们将创建并缓存强类型委托,而不是每次调用Foo<T>方法时使用纯反射。

int x = 42;
MyClass.Foo(x);    // displays "Non-Nullable Struct"

int? y = 123;
MyClass.Foo(y);    // displays "Nullable Struct"

string z = "Test";
MyClass.Foo(z);    // displays "Class"

// ...

public static class MyClass
{
    public static void Foo<T>(T? a) where T : struct
    {
        Console.WriteLine("Nullable Struct");
    }

    public static void Foo<T>(T a)
    {
        Type t = typeof(T);

        Delegate action;
        if (!FooDelegateCache.TryGetValue(t, out action))
        {
            MethodInfo mi = t.IsValueType ? FooWithStructInfo : FooWithClassInfo;
            action = Delegate.CreateDelegate(typeof(Action<T>), mi.MakeGenericMethod(t));
            FooDelegateCache.Add(t, action);
        }
        ((Action<T>)action)(a);
    }

    private static void FooWithStruct<T>(T a) where T : struct
    {
        Console.WriteLine("Non-Nullable Struct");
    }

    private static void FooWithClass<T>(T a) where T : class
    {
        Console.WriteLine("Class");
    }

    private static readonly MethodInfo FooWithStructInfo = typeof(MyClass).GetMethod("FooWithStruct", BindingFlags.NonPublic | BindingFlags.Static);
    private static readonly MethodInfo FooWithClassInfo = typeof(MyClass).GetMethod("FooWithClass", BindingFlags.NonPublic | BindingFlags.Static);
    private static readonly Dictionary<Type, Delegate> FooDelegateCache = new Dictionary<Type, Delegate>();
}

(注意这个例子不是线程安全的。如果你需要线程安全,那么你需要使用某种锁定围绕对缓存字典的所有访问,或者 - 如果你能够以.NET4为目标 - 改为使用ConcurrentDictionary<K,V>。)

答案 3 :(得分:5)

删除第一个方法上的struct约束。如果需要区分值类型和类,可以使用参数类型来执行此操作。

      static void Foo( T? a ) where T : struct
      {
         // nullable stuff here
      }

      static void Foo( T a )
      {
         if( a is ValueType )
         {
            // ValueType stuff here
         }
         else
         {
            // class stuff
         }
      }

答案 4 :(得分:2)

将我的评论放大到LukeH,如果需要使用Reflection来调用基于类型参数的不同动作(与对象实例的类型不同),那么这是一个有用的模式,就是创建一个私有的通用静态类,如以下(这个确切的代码未经测试,但我之前做过这样的事情):

static class FooInvoker<T>
{
  public Action<Foo> theAction = configureAction;
  void ActionForOneKindOfThing<TT>(TT param) where TT:thatKindOfThing,T
  {
    ...
  }
  void ActionForAnotherKindOfThing<TT>(TT param) where TT:thatOtherKindOfThing,T
  {
    ...
  }
  void configureAction(T param)
  {
    ... Determine which kind of thing T is, and set `theAction` to one of the
    ... above methods.  Then end with ...
    theAction(param);
  }
}

请注意,当ActionForOneKindOfThing<TT>(TT param)不符合该方法的约束时,如果有人试图为TT创建委托,则Reflection会抛出异常。由于系统在创建委托时验证了TT的类型,因此可以安全地调用theAction而无需进一步进行类型检查。另请注意,如果外部代码执行:

  FooInvoker<T>.theAction(param);

只有第一次通话才需要任何反射。后续调用将直接调用委托。

答案 5 :(得分:1)

如果您不需要通用参数,只想在编译时区分这三种情况,可以使用以下代码。

static void Foo(object a) { } // reference type
static void Foo<T>(T? a) where T : struct { } // nullable
static void Foo(ValueType a) { } // valuetype

答案 6 :(得分:0)

非常感谢C#7.3版不需要这种混乱的操作

请参见Whats new in C# 7.3-它不是很明确,但是现在看来在重载解析期间在某种程度上使用了“ where”参数。

  

超载解决方案现在具有较少的歧义情况

另请参见Visual Studio项目中的Selecting C# Version

它仍然会与以下内容冲突

password

但可以正确解决

Foo(x);
...
static void Foo<T>(T a) where T : class { } // 3
static void Foo<T>(T a) where T : struct { } // 3