tipo - usando generics c#
Llamada ambigua entre dos métodos genéricos de extensión C#uno donde T: clase y otro donde T: struct (3)
EDITAR: ahora he blogueado sobre esto en más detalle.
Mi pensamiento original (y ahora creo que es incorrecto): las restricciones genéricas no se tienen en cuenta durante la resolución de sobrecarga y las fases de inferencia tipo: solo se usan para validar el resultado de la resolución de sobrecarga.
EDITAR: Bien, después de un montón de vueltas en esto, creo que estoy allí. Básicamente mi primer pensamiento fue casi correcto.
Las restricciones de tipo genérico solo actúan para eliminar métodos de un conjunto candidato en un conjunto muy limitado de circunstancias ... en particular, solo cuando el tipo de un parámetro en sí es genérico; no solo un parámetro de tipo, sino un tipo genérico que usa un parámetro de tipo genérico. En ese punto, son las restricciones en los parámetros de tipo del tipo genérico los que se validan, no las restricciones en los parámetros de tipo del método genérico al que se llama.
Por ejemplo:
// 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
Entonces, si intentas llamar a Foo<object>(null)
el método anterior no será parte del conjunto candidato, porque el valor Nullable<object> value
no cumple con las restricciones de Nullable<T>
. Si hay otros métodos aplicables, la llamada aún podría tener éxito.
Ahora en el caso anterior, las restricciones son exactamente las mismas ... pero no necesitan serlo. Por ejemplo, considere:
class Factory<TItem> where TItem : new()
void Foo<T>(Factory<T> factory) where T : struct
Si intentas llamar a Foo<object>(null)
, el método seguirá formando parte del conjunto de candidatos, porque cuando TItem
es un object
, la restricción expresada en Factory<TItem>
aún se cumple, y eso es lo que se verifica al crear el candidato conjunto. Si este resulta ser el mejor método, fallará la validación más tarde, cerca del final de 7.6.5.1:
Si el mejor método es un método genérico, los argumentos de tipo (suministrados o inferidos) se comparan con las restricciones (§4.4.4) declaradas en el método genérico. Si cualquier argumento de tipo no satisface la (s) restricción (es) correspondiente (s) en el parámetro de tipo, se produce un error de tiempo de enlace.
La publicación del blog de Eric contiene más detalles sobre esto.
Considere dos métodos de extensión:
public static T MyExtension<T>(this T o) where T:class
public static T MyExtension<T>(this T o) where T:struct
Y una clase:
class MyClass() { ... }
Ahora llama al método de extensión en una instancia de la clase anterior:
var o = new MyClass(...);
o.MyExtension(); //compiler error here..
o.MyExtension<MyClass>(); //tried this as well - still compiler error..
El compilador dice que llamar al método es una llamada ambigua cuando lo llamo en una clase. Hubiera pensado que podría determinar qué método de extensión llamar, ya que MyClass es una clase, no una estructura.
Encontré esta "extraña" manera extraña de hacer eso en .NET 4.5 usando valores de parámetros predeterminados :) Tal vez es más útil para fines educativos / especulativos que para el uso real, pero me gustaría mostrarlo:
/// <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();
}
}
Eric Lippert lo explica mejor que yo, aquí .
Me he encontrado con esto yo mismo. Mi solución fue
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)