Tipo anulável como um parâmetro genérico possível?

287

Eu quero fazer algo assim:

myYear = record.GetValueOrNull<int?>("myYear"),

Observe o tipo anulável como o parâmetro genérico.

Como a GetValueOrNullfunção pode retornar nula, minha primeira tentativa foi a seguinte:

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : class
{
    object columnValue = reader[columnName];

    if (!(columnValue is DBNull))
    {
        return (T)columnValue;
    }
    return null;
}

Mas o erro que estou recebendo agora é:

O tipo 'int?' deve ser um tipo de referência para usá-lo como parâmetro 'T' no tipo ou método genérico

Certo! Nullable<int>é um struct! Então, tentei alterar a restrição de classe para uma structrestrição (e como efeito colateral não pode mais retornar null):

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : struct

Agora a tarefa:

myYear = record.GetValueOrNull<int?>("myYear");

Dá o seguinte erro:

O tipo 'int?' deve ser um tipo de valor não anulável para usá-lo como parâmetro 'T' no tipo ou método genérico

É possível especificar um tipo anulável como um parâmetro genérico?

Tom Pester
fonte
3
Pls pls fazer a sua assinatura IDataRecorda partir de DbDataRecord..
Nawfal

Respostas:

262

Altere o tipo de retorno para Nullable e chame o método com o parâmetro não nulo

static void Main(string[] args)
{
    int? i = GetValueOrNull<int>(null, string.Empty);
}


public static Nullable<T> GetValueOrNull<T>(DbDataRecord reader, string columnName) where T : struct
{
    object columnValue = reader[columnName];

    if (!(columnValue is DBNull))
        return (T)columnValue;

    return null;
}
Greg Dean
fonte
1
Eu sugiro que você use "columnValue == DBNull.Value" em vez do operador 'é', porque o seu ligeiramente mais rápido =)
Drian
40
Preferência pessoal, mas você pode usar o formato abreviado T? em vez de Nullable <T>
Dunc 9/09/10
11
Isso é bom para tipos de valor, mas acho que não funcionará de maneira alguma com tipos de referência (por exemplo, GetValueOrNull <string>) porque o C # não parece Nullable <(ref type)> como "string?". As soluções de Robert C Barth e James Jones, abaixo, parecem muito melhores para mim, se essa for sua necessidade.
Bacar
2
@bacar - direita, daí o "onde T: struct", se você quiser tipos de referência que você pode criar um método semelhante com "onde T: class"
Greg Dean
4
@ Greg - claro, mas você precisa de um segundo método e não pode sobrecarregar o nome. Como eu disse, se você deseja lidar com os tipos val e ref, acho que soluções mais limpas são apresentadas nesta página.
Bacar
107
public static T GetValueOrDefault<T>(this IDataRecord rdr, int index)
{
    object val = rdr[index];

    if (!(val is DBNull))
        return (T)val;

    return default(T);
}

Apenas use-o assim:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);
James Jones
fonte
6
Isso pode ser reduzido para: return rdr.IsDBNull (index)? padrão (T): (T) rdr [index];
Foole
11
Eu acho que essa pergunta quer explicitamente nulo , não padrão (T) .
mafu
5
@mafu default (T) retornará nulo para tipos de referência e 0 para tipos numéricos, tornando a solução mais flexível.
James Jones
2
Eu acho que é mais claro chamar isso GetValueOrDefaultpara esclarecer que ele retorna em default(T)vez de null. Como alternativa, você pode emitir uma exceção se Tnão for anulável.
Sam
Esse método tem muitas vantagens e obriga a pensar em retornar algo diferente de nulo também.
21418 Shane
61

Faça duas coisas no código original - remova a whererestrição e altere a última returnde return nullpara return default(T). Dessa forma, você pode retornar o tipo que desejar.

A propósito, você pode evitar o uso isalterando sua ifdeclaração para if (columnValue != DBNull.Value).

Robert C. Barth
fonte
4
Esta solução não funciona, como há uma diferença lógica entre NULL e 0
Greg Dean
15
Funciona se o tipo que ele passa é int ?. Ele retornará NULL, exatamente como ele deseja. Se ele passar int como o tipo, ele retornará 0, pois um int não pode ser NULL. Além do fato de que eu tentei e funciona perfeitamente.
Robert C. Barth
2
Essa é a resposta mais correta e flexível. No entanto, return defaulté suficiente (você não precisa do (T), o compilador o inferirá do tipo de retorno de assinatura).
precisa saber é o seguinte
5

Isenção de responsabilidade: Esta resposta funciona, mas destina-se apenas a fins educacionais. :) A solução de James Jones é provavelmente a melhor aqui e certamente a que eu escolheria.

A dynamicpalavra-chave do C # 4.0 torna isso ainda mais fácil, se menos seguro:

public static dynamic GetNullableValue(this IDataRecord record, string columnName)
{
  var val = reader[columnName];

  return (val == DBNull.Value ? null : val);
}

Agora você não precisa da dica explícita de tipo no RHS:

int? value = myDataReader.GetNullableValue("MyColumnName");

Na verdade, você nem precisa disso!

var value = myDataReader.GetNullableValue("MyColumnName");

value agora será um int, ou uma string ou qualquer tipo que foi retornado do banco de dados.

O único problema é que isso não impede que você use tipos não anuláveis ​​no LHS; nesse caso, você receberá uma exceção de tempo de execução bastante desagradável, como:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot convert null to 'int' because it is a non-nullable value type

Tal como acontece com todo o código que usa dynamic: caveat coder.

Ian Kemp
fonte
4

Só tinha que fazer algo incrível semelhante a isso. Meu código:

public T IsNull<T>(this object value, T nullAlterative)
{
    if(value != DBNull.Value)
    {
        Type type = typeof(T);
        if (type.IsGenericType && 
            type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
        {
            type = Nullable.GetUnderlyingType(type);
        }

        return (T)(type.IsEnum ? Enum.ToObject(type, Convert.ToInt32(value)) :
            Convert.ChangeType(value, type));
    }
    else 
        return nullAlternative;
}
Toby
fonte
3

Eu acho que você deseja lidar com tipos de referência e tipos de estrutura. Eu o uso para converter seqüências de caracteres de elemento XML em um tipo mais digitado. Você pode remover o nullAlternative com reflexão. O provedor de formato é lidar com a cultura dependente '.' ou ',' separador, por exemplo, decimais ou ints e duplos. Isso pode funcionar:

public T GetValueOrNull<T>(string strElementNameToSearchFor, IFormatProvider provider = null ) 
    {
        IFormatProvider theProvider = provider == null ? Provider : provider;
        XElement elm = GetUniqueXElement(strElementNameToSearchFor);

        if (elm == null)
        {
            object o =  Activator.CreateInstance(typeof(T));
            return (T)o; 
        }
        else
        {
            try
            {
                Type type = typeof(T);
                if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
                {
                    type = Nullable.GetUnderlyingType(type);
                }
                return (T)Convert.ChangeType(elm.Value, type, theProvider); 
            }
            catch (Exception)
            {
                object o = Activator.CreateInstance(typeof(T));
                return (T)o; 
            }
        }
    }

Você pode usá-lo assim:

iRes = helper.GetValueOrNull<int?>("top_overrun_length");
Assert.AreEqual(100, iRes);



decimal? dRes = helper.GetValueOrNull<decimal?>("top_overrun_bend_degrees");
Assert.AreEqual(new Decimal(10.1), dRes);

String strRes = helper.GetValueOrNull<String>("top_overrun_bend_degrees");
Assert.AreEqual("10.1", strRes);
Roland Roos
fonte
2

Isso pode ser um fio morto, mas eu costumo usar o seguinte:

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct 
{
    return reader[columnName] as T?;
}
Ryan Horch
fonte
1
"O tipo 'T' deve ser um tipo de valor não nulo para poder usá-lo como parâmetro 'T' no tipo ou método genérico 'Nullable <T>'"
Ian Warburton
1

Acabei de encontrar o mesmo problema.

... = reader["myYear"] as int?; funciona e está limpo.

Funciona com qualquer tipo sem problemas. Se o resultado for DBNull, ele retornará nulo quando a conversão falhar.

Hele
fonte
Na verdade, você provavelmente poderia fazer int v=reader["myYear"]??-1;ou algum outro padrão em vez de -1. No entanto, isso pode trazer problemas se o valor for DBNull...
nurchi
1

Eu sei que isso é antigo, mas aqui está outra solução:

public static bool GetValueOrDefault<T>(this SqlDataReader Reader, string ColumnName, out T Result)
{
    try
    {
        object ColumnValue = Reader[ColumnName];

        Result = (ColumnValue!=null && ColumnValue != DBNull.Value) ? (T)ColumnValue : default(T);

        return ColumnValue!=null && ColumnValue != DBNull.Value;
    }
    catch
    {
        // Possibly an invalid cast?
        return false;
    }
}

Agora, você não se importa se Tfoi o valor ou o tipo de referência. Somente se a função retornar true, você terá um valor razoável no banco de dados. Uso:

...
decimal Quantity;
if (rdr.GetValueOrDefault<decimal>("YourColumnName", out Quantity))
{
    // Do something with Quantity
}

Essa abordagem é muito semelhante à int.TryParse("123", out MyInt);

Nurchi
fonte
Seria bom se você trabalhasse em suas convenções de nomenclatura. Eles não têm consistência. Em um lugar, há uma variável sem capital, e há uma com. O mesmo com parâmetros para os métodos.
Marino Šimić 25/11
1
Feito e feito! O código de esperança parece melhor agora. Bob é sua tia :) Tudo é skookum
nurchi
0

Múltiplas restrições genéricas não podem ser combinadas de maneira OR (menos restritivas), apenas de maneira AND (mais restritivas). Significando que um método não pode lidar com os dois cenários. As restrições genéricas também não podem ser usadas para criar uma assinatura exclusiva para o método, portanto, você deve usar 2 nomes de métodos separados.

No entanto, você pode usar as restrições genéricas para garantir que os métodos sejam usados ​​corretamente.

No meu caso, eu queria especificamente que o nulo fosse retornado e nunca o valor padrão de qualquer tipo de valor possível. GetValueOrDefault = incorreto. GetValueOrNull = bom.

Usei as palavras "Nulo" e "Nulo" para distinguir entre tipos de referência e tipos de valor. E aqui está um exemplo de alguns métodos de extensão que escrevi que complementam o método FirstOrDefault na classe System.Linq.Enumerable.

    public static TSource FirstOrNull<TSource>(this IEnumerable<TSource> source)
        where TSource: class
    {
        if (source == null) return null;
        var result = source.FirstOrDefault();   // Default for a class is null
        return result;
    }

    public static TSource? FirstOrNullable<TSource>(this IEnumerable<TSource?> source)
        where TSource : struct
    {
        if (source == null) return null;
        var result = source.FirstOrDefault();   // Default for a nullable is null
        return result;
    }
Casey Plummer
fonte
0

A maneira mais curta:

public static T ValueOrDefault<T>(this DataRow reader, string columnName) => 
        reader.IsNull(columnName) ? default : (T) reader[columnName];

retornar 0para inte nullparaint?

Amirhossein Yari
fonte