¿Cómo puedo devolver NULL de un método genérico en C #?

546

Tengo un método genérico con este código (ficticio) (sí, sé que IList tiene predicados, pero mi código no está usando IList sino alguna otra colección, de todos modos esto es irrelevante para la pregunta ...)

static T FindThing<T>(IList collection, int id) where T : IThing, new()
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return thing;
    }
    return null;  // ERROR: Cannot convert null to type parameter 'T' because it could be a value type. Consider using 'default(T)' instead.
}

Esto me da un error de compilación

"No se puede convertir nulo al parámetro de tipo 'T' porque podría ser un tipo de valor. Considere usar 'default (T)' en su lugar".

¿Puedo evitar este error?

edosoft
fuente
¿Serían los tipos de referencia anulables (en C # 8) la mejor solución para esto ahora? docs.microsoft.com/en-us/dotnet/csharp/nullable-references Volviendo nullindependientemente de si Tes Objecto into char.
Alexander - Restablece a Monica

Respuestas:

969

Dos opciones:

  • Retorno, lo default(T)que significa que devolverá nullsi T es un tipo de referencia (o un tipo de valor anulable), 0para int, '\0'para char, etc. ( Tabla de valores predeterminados (Referencia de C #) )
  • Restrinja T para que sea un tipo de referencia con la where T : classrestricción y luego regrese nullcomo normal
Jon Skeet
fuente
3
¿Qué sucede si mi tipo de devolución es una enumeración, no una clase? No puedo especificar T: enum :(
Justin
1
En .NET, una enumeración es un contenedor muy delgado (y bastante permeable) alrededor de un tipo entero. La convención es usar cero para su valor de enumeración "predeterminado".
Mike Chamberlain
27
Creo que el problema con esto es que si está utilizando este método genérico para decir, convierta un objeto de base de datos de DbNull a Int y devuelva el valor predeterminado (T) donde T es un int, devolverá 0. Si este número es realmente significativo, entonces estaría pasando datos incorrectos en los casos en que ese campo fuera nulo. O un mejor ejemplo sería un DateTime. Si el campo era algo así como "DateClosed" y se devolvió como nulo porque la cuenta todavía está abierta, en realidad (DateTime) sería por defecto el 1/1/0000, lo que implica que la cuenta se cerró antes de que se inventaran las computadoras.
Sinaesthetic
21
@Sinaesthetic: Así que es convertir a Nullable<int>, o Nullable<DateTime>en su lugar. Si usa un tipo no anulable y necesita representar un valor nulo, solo está buscando problemas.
Jon Skeet
1
Estoy de acuerdo, solo quería mencionarlo. Creo que lo que he estado haciendo es más como MyMethod <T> (); asumir que es un tipo no anulable y MyMethod <T?> (); asumir que es un tipo anulable. Entonces, en mis escenarios, podría usar una variable temporal para atrapar un valor nulo e ir desde allí.
Sinaesthetic
84
return default(T);
Ricardo Villamil
fuente
Este enlace: msdn.microsoft.com/en-us/library/xwth0h0d(VS.80).aspx debería explicar por qué.
Harper Shelby
1
Maldición, habría ahorrado mucho tiempo si hubiera sabido sobre esta palabra clave. ¡Gracias Ricardo!
Ana Betts
1
Me sorprende que esto no haya obtenido más votos, ya que la palabra clave 'predeterminada' es una solución más completa, que permite el uso de tipos sin referencia junto con tipos numéricos y estructuras. Si bien la respuesta aceptada resuelve el problema (y de hecho es útil), responde mejor cómo restringir el tipo de retorno a tipos anulables / de referencia.
Steve Jackson
33

Simplemente puede ajustar sus restricciones:

where T : class

Luego, devolver nulo está permitido.

TheSoftwareJedi
fuente
Gracias. No puedo elegir 2 respuestas como la solución aceptada, así que elijo la respuesta de John Skeet porque su respuesta tiene dos soluciones.
edosoft el
@Migol depende de sus requisitos. Tal vez su proyecto lo requiera IDisposable. Sí, la mayoría de las veces no tiene que ser así. System.Stringno implementa IDisposable, por ejemplo. El respondedor debería haber aclarado eso, pero eso no hace que la respuesta sea incorrecta. :)
ahwm
@Migol No tengo idea de por qué tenía IDisposable allí. Remoto.
TheSoftwareJedi
13

Agregue la restricción de clase como la primera restricción a su tipo genérico.

static T FindThing<T>(IList collection, int id) where T : class, IThing, new()
Min
fuente
Gracias. No puedo elegir 2 respuestas como la solución aceptada, así que elijo la respuesta de John Skeet porque su respuesta tiene dos soluciones.
edosoft
7
  1. Si tiene un objeto, entonces necesita escribir

    return (T)(object)(employee);
  2. si necesita devolver nulo.

    return default(T);
usuario725388
fuente
Hola usuario725388, verifique la primera opción
Jogi Joseph George
7

A continuación se encuentran las dos opciones que puede usar

return default(T);

o

where T : class, IThing
 return null;
Jaydeep Shil
fuente
6

Su otra opción sería agregar esto al final de su declaración:

    where T : class
    where T: IList

De esa manera te permitirá volver nulo.

BFree
fuente
Si ambas restricciones son del mismo tipo, mencione el tipo una vez y use una coma, como where T : class, IList. Si tiene restricciones para diferentes tipos, repita el token where, como en where TFoo : class where TBar : IList.
Jeppe Stig Nielsen
3

solución de TheSoftwareJedi funciona,

También puede archivarlo usando un par de valores y tipos anulables:

static T? FindThing<T>(IList collection, int id) where T : struct, IThing
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return thing;
    }
    return null;
}
devi
fuente
1

Tome la recomendación del error ... y ya sea usuario default(T)o new T.

Tendrás que agregar una comparación en tu código para asegurarte de que sea una coincidencia válida si sigues esa ruta.

De lo contrario, considere potencialmente un parámetro de salida para "coincidencia encontrada".

vendedores de Mitchel
fuente
1

Aquí hay un ejemplo de trabajo para los valores de retorno de Nullable Enum:

public static TEnum? ParseOptional<TEnum>(this string value) where TEnum : struct
{
    return value == null ? (TEnum?)null : (TEnum) Enum.Parse(typeof(TEnum), value);
}
Luke
fuente
Desde C # 7.3 (mayo de 2018), puede mejorar la restricción para where TEnum : struct, Enum. Esto garantiza que la persona que llama no proporcione accidentalmente un tipo de valor que no sea una enumeración (como una into una DateTime).
Jeppe Stig Nielsen
0

Otra alternativa a las 2 respuestas presentadas anteriormente. Si cambia su tipo de retorno a object, puede regresar null, al mismo tiempo que emite el retorno no nulo.

static object FindThing<T>(IList collection, int id)
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return (T) thing;
    }
    return null;  // allowed now
}
Jeson Martajaya
fuente
Desventaja: esto requeriría que el llamador del método arroje el objeto devuelto (en caso no nulo), lo que implica el boxeo -> menos rendimiento. Estoy en lo cierto?
Csharpest
0

Para completar, es bueno saber que también podrías hacer esto:

return default;

Devuelve lo mismo que return default(T);

LCIII
fuente
0

Para mí funciona como es. ¿Dónde está exactamente el problema?

public static T FindThing<T>(this IList collection, int id) where T : IThing, new()
{
    foreach (T thing in collection)
    {
        if (thing.Id == id)
            return thing;
        }
    }

    return null; //work
    return (T)null; //work
    return null as T; //work
    return default(T); //work


}
Mertuarez
fuente