代码之家  ›  专栏  ›  技术社区  ›  Pierre Arnaud

泛型约束,其中t:struct和where t:class

  •  44
  • Pierre Arnaud  · 技术社区  · 14 年前

    我想区分以下情况:

    1. 普通值类型(例如 int )
    2. 可为空的值类型(例如 int? )
    3. 参考类型(例如 string )-或者,我不在乎这是否映射到上面的(1)或(2)

    我已经想出了以下代码,对于第(1)和(2)种情况下都可以使用:

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

    但是,如果我尝试检测这种情况(3),它不会编译:

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

    错误消息是 类型“x”已经用相同的参数类型定义了一个名为“foo”的成员 . 好吧,不知怎的,我无法区分 where T : struct where 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 回复  |  直到 6 年前
        1
  •  47
  •   Alcaro    8 年前

    约束不是签名的一部分,但参数是。在过载解决过程中强制执行参数中的约束。

    所以让我们把约束放到一个参数中。它很难看,但很管用。

    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
    

    (迟六年总比不迟一年好吗?)

        2
  •  19
  •   Lasse V. Karlsen    14 年前

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

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

        3
  •  10
  •   Community CDub    7 年前

    关于你的评论 Marnix's answer 你可以通过一点反思来实现你想要的。

    在下面的示例中,不受约束的 Foo<T> 方法使用反射将调用分为适当的约束方法-或者 FooWithStruct<T> FooWithClass<T> . 出于性能原因,我们将创建和缓存强类型委托,而不是每次 FO & 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>();
    }
    

    (注意 此示例不是threadsafe . 如果您需要线程安全,那么您要么需要在对缓存字典的所有访问周围使用某种类型的锁定,要么——如果您能够以.net4为目标——使用 ConcurrentDictionary<K,V> 取而代之的是)

        4
  •  5
  •   Marnix van Valen    14 年前

    删除第一个方法的结构约束。如果需要区分值类型和类,可以使用参数的类型来区分。

          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
             }
          }
    
        5
  •  2
  •   supercat    13 年前

    将我的注释放大到lukeh,如果需要使用反射来调用基于类型参数的不同操作(与对象实例的类型不同),那么一个有用的模式是创建一个私有的通用静态类,类似于以下内容(这个确切的代码是未经测试的,但我以前做过这种事情):

    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 不符合该方法的约束。因为系统验证了 TT 创建委托时,可以安全地调用 theAction 无需进一步的类型检查。还要注意,如果外部代码有:

      FooInvoker<T>.theAction(param);
    

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

        6
  •  1
  •   Daniel Kozłowski    9 年前

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

    static void Foo(object a) { } // reference type
    static void Foo<T>(T? a) where T : struct { } // nullable
    static void Foo(ValueType a) { } // valuetype
    
        7
  •  0
  •   Sprotty    6 年前

    值得庆幸的是,从C版本7.3开始,这种混乱不需要那么多。

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

    过载解决方案现在具有较少的不明确情况

    也看到 Selecting C# Version 在您的Visual Studio项目中

    它仍将看到与以下内容的冲突

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

    但会正确解决

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