Перегрузка метода по ограничениям типа в C #? [Дубликат]

Интересно, что ни один из ответов на этой странице не упоминает два крайних случая, надеюсь, никто не возражает, если я их добавлю:

Случай с краем # 1: одновременный доступ к Словарю

Родовые словари в .NET не являются потокобезопасными, а иногда могут бросать NullReference или даже (чаще) a KeyNotFoundException при попытке получить доступ к ключу из двух параллельных потоков. Исключение в этом случае является довольно ошибочным.

Случай с краем # 2: небезопасный код

Если код NullReferenceException задан кодом unsafe, вы можете посмотреть на переменные указателя , и проверьте их на IntPtr.Zero или что-то в этом роде. Это одно и то же («исключение нулевого указателя»), но в небезопасном коде переменные часто переводятся в типы значений / массивы и т. Д., И вы ударяете головой о стену, задаваясь вопросом, как тип значения может исключение.

(Еще одна причина для небезопасного использования небезопасного кода, если вам это нужно)

42
задан Pierre Arnaud 4 June 2010 в 14:23
поделиться

6 ответов

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

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

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

(лучше на шесть лет позже, чем никогда?)

42
ответ дан Alcaro 27 August 2018 в 11:38
поделиться

В дополнение к вашему комментарию к ответу 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> .)

10
ответ дан Community 27 August 2018 в 11:38
поделиться
  • 1
    Можно ли улучшить ситуацию, используя подход, подобный Comparer<T>.Default, например. создать частный статический общий класс FooInvoker<T> с открытым полем FooMethod типа Action<T> (поскольку FooInvoker<T> будет недоступным вне MyClass, не будет никакого риска внешнего кода, злоупотребляющего публичным полем)? Если конструктор класса для FooInvoker<T> правильно устанавливает FooMethod, я думаю, что это могло бы избежать необходимости поиска словаря во время выполнения (я не знаю, нужно ли будет выполнять .net каждый раз внутри каждого раза Foo<T> называется). – supercat 22 February 2012 в 20:09
  • 2
    См. Мой опубликованный ответ для описания того, как использовать статический класс. Я, вероятно, сделал некоторые синтаксические ошибки, так как я печатаю из памяти (и в основном программу на vb.net), но должно быть достаточно контура, чтобы вы могли двигаться. – supercat 26 February 2012 в 20:45

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

static void Foo(object a) { } // reference type
static void Foo<T>(T? a) where T : struct { } // nullable
static void Foo(ValueType a) { } // valuetype
1
ответ дан Daniel Kozłowski 27 August 2018 в 11:38
поделиться

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

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

19
ответ дан Lasse Vågsæther Karlsen 27 August 2018 в 11:38
поделиться
  • 1
    +1. Конечно, первая и вторая работа, потому что T и T? - разные аргументы. (T и Nullable<T>) – Powerlord 4 June 2010 в 14:28
  • 2
  • 3
    Спасибо вам за ваш быстрый ответ; если я не могу различать типы, есть ли способ получить мой последний пример для компиляции, ослабляя некоторые ограничения? – Pierre Arnaud 4 June 2010 в 14:43
  • 4
    Ах, просто опустите where T : struct для метода (1), и мой пример компилируется. Этого достаточно для меня. – Pierre Arnaud 4 June 2010 в 14:44
  • 5
    На самом деле, можно различать тип вызова метода на основе ограничений, если нет возражений против наличия фиктивного «необязательного» кода. параметр общего ссылочного типа, который имеет ограничения на его общие аргументы и имеет "нулевой" значение по умолчанию для этого параметра. Поскольку компилятор исключает из рассмотрения любую перегрузку, тип которой не может быть сконструирован, ограничения в типе фиктивного параметра будут эффективны для ограничения того, какие перегрузки рассматриваются. Конечно, если компилятор может сделать это на сайте вызова, который не дает значения для фиктивного аргумента, он ... – supercat 21 April 2016 в 18:57

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

      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
ответ дан Marnix van Valen 27 August 2018 в 11:38
поделиться
  • 1
    @Maxim: Спасибо. Проблема, с которой я сталкиваюсь, заключается в том, что в методе, не допускающем nullable, я должен иметь возможность ссылаться на другие функции, которые принимают и возвращают T?, и это недействительно без ограничения where T : struct. – Pierre Arnaud 4 June 2010 в 15:05

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

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

2
ответ дан supercat 27 August 2018 в 11:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: