Интересно, что ни один из ответов на этой странице не упоминает два крайних случая, надеюсь, никто не возражает, если я их добавлю:
Родовые словари в .NET не являются потокобезопасными, а иногда могут бросать NullReference
или даже (чаще) a KeyNotFoundException
при попытке получить доступ к ключу из двух параллельных потоков. Исключение в этом случае является довольно ошибочным.
Если код NullReferenceException
задан кодом unsafe
, вы можете посмотреть на переменные указателя , и проверьте их на IntPtr.Zero
или что-то в этом роде. Это одно и то же («исключение нулевого указателя»), но в небезопасном коде переменные часто переводятся в типы значений / массивы и т. Д., И вы ударяете головой о стену, задаваясь вопросом, как тип значения может исключение.
(Еще одна причина для небезопасного использования небезопасного кода, если вам это нужно)
Ограничения не являются частью подписи, но являются параметрами. И ограничения в параметрах применяются во время разрешения перегрузки.
Итак, давайте поместим ограничение в параметр. Это уродливо, но оно работает.
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
(лучше на шесть лет позже, чем никогда?)
В дополнение к вашему комментарию к ответу 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>
.)
Comparer<T>.Default
, например. создать частный статический общий класс FooInvoker<T>
с открытым полем FooMethod
типа Action<T>
(поскольку FooInvoker<T>
будет недоступным вне MyClass
, не будет никакого риска внешнего кода, злоупотребляющего публичным полем)? Если конструктор класса для FooInvoker<T>
правильно устанавливает FooMethod
, я думаю, что это могло бы избежать необходимости поиска словаря во время выполнения (я не знаю, нужно ли будет выполнять .net каждый раз внутри каждого раза Foo<T>
называется).
– supercat
22 February 2012 в 20:09
Если вам не нужны общие параметры и вы просто хотите различать эти 3 случая во время компиляции, вы можете использовать следующий код.
static void Foo(object a) { } // reference type
static void Foo<T>(T? a) where T : struct { } // nullable
static void Foo(ValueType a) { } // valuetype
К сожалению, вы не можете различать тип метода для вызова, основанный только на ограничениях.
Поэтому вам нужно определить метод в другом классе или с другим именем.
T
и T?
- разные аргументы. (T
и Nullable<T>
)
– Powerlord
4 June 2010 в 14:28
where T : struct
для метода (1), и мой пример компилируется. Этого достаточно для меня.
– Pierre Arnaud
4 June 2010 в 14:44
Снимите ограничение структуры на первый метод. Если вам нужно различать типы значений и классы, вы можете использовать тип аргумента для этого.
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
}
}
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);
, для первого вызова потребуется любое отражение. Последующие вызовы просто вызовут делегата напрямую.
ignore
ко второй функцииFoo<T>
, принимающейT?
. – Pierre Arnaud 24 April 2016 в 06:57