Подтвердить что ты не робот

Общие ограничения, где T: struct и где T: class

Я хотел бы различать следующие случаи:

  • Тип простого значения (например, int)
  • Тип с нулевым значением (например, int?)
  • Тип ссылки (например, 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) для компиляции, сопоставляя его с одной из вышеперечисленных функций (или третьей с другим ограничением, о котором я не думал)?

4b9b3361

Ответ 1

Ограничения не являются частью подписи, но являются параметрами. И ограничения в параметрах применяются во время разрешения перегрузки.

Итак, давайте поставьте ограничение в параметр. Это уродливо, но оно работает.

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

Вы, к сожалению, не можете различать тип метода для вызова, основанный только на ограничениях.

Поэтому вам нужно определить метод в другом классе или с другим именем.

Ответ 3

В ответ на комментарий Marnix вы можете добиться того, чего хотите, используя немного отражения.

В приведенном ниже примере неограниченный метод 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> вместо.)

Ответ 4

Отбросить конструктивное отклонение по первому методу. Если вам нужно различать типы значений и классы, вы можете использовать тип аргумента для этого.

      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

Усиление моего комментария к 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);
  }
}

Обратите внимание, что Reflection генерирует исключение, если вы попытаетесь создать делегат для ActionForOneKindOfThing<TT>(TT param), если TT не соответствует ограничениям этого метода. Поскольку система подтвердила тип TT при создании делегата, можно безопасно вызвать theAction без дальнейшей проверки типов. Также обратите внимание, что если внешний код делает:

  FooInvoker<T>.theAction(param);

только для первого вызова потребуется любое отражение. Последующие вызовы просто вызовут делегат напрямую.

Ответ 6

Если вам не нужны общие параметры и просто нужно различать эти 3 случая во время компиляции, вы можете использовать следующий код.

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