Неоднозначный вызов между двумя универсальными методами расширения С#, где T: class и другие, где T: struct
Рассмотрим два метода расширения:
public static T MyExtension<T>(this T o) where T:class
public static T MyExtension<T>(this T o) where T:struct
И класс:
class MyClass() { ... }
Теперь вызовите метод расширения в экземпляре указанного класса:
var o = new MyClass(...);
o.MyExtension(); //compiler error here..
o.MyExtension<MyClass>(); //tried this as well - still compiler error..
Компилятор говорит, что вызов метода является неоднозначным вызовом, когда я вызываю его в классе. Я бы подумал, что он может определить, какой метод расширения вызывать, поскольку MyClass - это класс, а не структура?
Ответы
Ответ 1
EDIT: теперь я написал об этом более подробно.
Мое оригинальное (и я теперь считаю неверным) мысль: общие ограничения не учитываются во время разрешения перегрузки и типа вывода - они используются только для проверки результата разрешения перегрузки.
РЕДАКТИРОВАТЬ: Хорошо, после многого на этом, я думаю, что я там. В основном моя первая мысль была почти правильной.
Ограничения общего типа действуют только для удаления методов из набора кандидатов в очень ограниченном наборе обстоятельств... в частности, только в том случае, когда тип самого параметра является общим; а не только параметр типа, а общий тип, который использует параметр типового типа. В этот момент это ограничения на параметры типа общего типа, которые проверяются, а не ограничения на параметры типа общего метода, который вы вызываете.
Например:
// Constraint won't be considered when building the candidate set
void Foo<T>(T value) where T : struct
// The constraint *we express* won't be considered when building the candidate
// set, but then constraint on Nullable<T> will
void Foo<T>(Nullable<T> value) where T : struct
Поэтому, если вы попытаетесь вызвать Foo<object>(null)
, указанный выше метод не будет частью набора кандидатов, потому что Nullable<object> value
не удовлетворяет ограничениям Nullable<T>
. Если есть другие применимые методы, вызов все равно может быть успешным.
Теперь в приведенном выше случае ограничения одинаковы... но они не обязательно должны быть. Например, рассмотрим:
class Factory<TItem> where TItem : new()
void Foo<T>(Factory<T> factory) where T : struct
Если вы попытаетесь вызвать Foo<object>(null)
, этот метод по-прежнему будет частью набора кандидатов, потому что когда TItem
равно object
, ограничение, выраженное в Factory<TItem>
, сохраняется, и это то, что проверяется при создании кандидат установлен. Если это окажется лучшим методом, он позже завершит проверку, ближе к концу 7.6.5.1:
Если лучшим методом является общий метод, аргументы типа (поставляемые или выводимые) проверяются на ограничения (§4.4.4), объявленные в общем методе. Если какой-либо аргумент типа не удовлетворяет соответствующему ограничению (ограничениям) в параметре типа, возникает ошибка времени привязки.
Eric сообщение в блоге содержит более подробную информацию об этом.
Ответ 2
Эрик Липперт объясняет лучше, чем когда-либо, здесь.
Я сам наткнулся на это. Мое решение было
public void DoSomthing<T> (T theThing){
if (typeof (T).IsValueType)
DoSomthingWithStruct (theThing);
else
DoSomthingWithClass (theThing);
}
// edit - seems I just lived with boxing
public void DoSomthingWithStruct (object theThing)
public void DoSomthingWithClass(object theThing)
Ответ 3
Я нашел этот "интересный" странный способ сделать это в .NET 4.5, используя значения параметров по умолчанию:) Возможно, это более полезно для образовательных\спекулятивных целей, чем для реального использования, но я хотел бы показать это:
/// <summary>Special magic class that can be used to differentiate generic extension methods.</summary>
public class MagicValueType<TBase>
where TBase : struct
{
}
/// <summary>Special magic class that can be used to differentiate generic extension methods.</summary>
public class MagicRefType<TBase>
where TBase : class
{
}
struct MyClass1
{
}
class MyClass2
{
}
// Extensions
public static class Extensions
{
// Rainbows and pink unicorns happens here.
public static T Test<T>(this T t, MagicRefType<T> x = null)
where T : class
{
Console.Write("1:" + t.ToString() + " ");
return t;
}
// More magic, other pink unicorns and rainbows.
public static T Test<T>(this T t, MagicValueType<T> x = null)
where T : struct
{
Console.Write("2:" + t.ToString() + " ");
return t;
}
}
class Program
{
static void Main(string[] args)
{
MyClass1 t1 = new MyClass1();
MyClass2 t2 = new MyClass2();
MyClass1 t1result = t1.Test();
Console.WriteLine(t1result.ToString());
MyClass2 t2result = t2.Test();
Console.WriteLine(t2result.ToString());
Console.ReadLine();
}
}