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




generics (9)

Basta fazer duas coisas no seu código original - remova a restrição where e altere o último return de return null para return default(T) . Desta forma, você pode retornar o tipo que quiser.

By the way, você pode evitar o uso de is mudando sua declaração if (columnValue != DBNull.Value) para if (columnValue != DBNull.Value) .

Eu quero fazer algo assim:

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

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

Como a função GetValueOrNull poderia retornar null, minha primeira tentativa foi esta:

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 genérico ou método

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

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 genérico ou método

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


Várias restrições genéricas não podem ser combinadas de uma maneira OR (menos restritiva), apenas em uma forma AND (mais restritiva). O que significa que um método não pode lidar com ambos os 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ê teria que usar dois nomes de método 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 null fosse retornado e nunca o valor padrão de qualquer tipo de valor possível. GetValueOrDefault = ruim. GetValueOrNull = bom.

Usei as palavras "Null" e "Nullable" 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;
    }

Apenas tive 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;
}

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 assim:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);

Eu acabei de encontrar o mesmo problema eu mesmo.

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

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


Isso pode ser um thread 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?;
}

Eu acho que você quer lidar com tipos de referência e tipos de estrutura. Eu uso para converter seqüências de elementos XML para um tipo mais digitado. Você pode remover o nullAlternative com reflexão. O formatprovider é para lidar com a cultura dependente '.' ou ',' separador em por exemplo casas decimais ou ints e duplas. 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);

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 T foi valor ou 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 a int.TryParse("123", out MyInt);


Como todos apontaram, uma equipe de jogadores não é uma lista de jogadores. Este erro é cometido por muitas pessoas em todos os lugares, talvez em vários níveis de especialização. Muitas vezes o problema é sutil e ocasionalmente muito grosseiro, como neste caso. Tais projetos são ruins porque estes violam o Princípio de Substituição de Liskov . A internet tem muitos bons artigos explicando esse conceito, por exemplo, http://en.wikipedia.org/wiki/Liskov_substitution_principle

Em resumo, existem duas regras a serem preservadas em um relacionamento pai / filho entre classes:

  • Uma criança não deve exigir nenhuma característica menor do que o que define completamente o pai.
  • Um pai não deve exigir nenhuma característica além do que define completamente a criança.

Em outras palavras, um pai é uma definição necessária de um filho, e um filho é uma definição suficiente de um pai.

Aqui está uma maneira de pensar através da solução e aplicar o princípio acima que deve ajudar a evitar esse erro. Deve-se testar as hipóteses verificando se todas as operações de uma classe pai são válidas para a classe derivada estrutural e semanticamente.

  • Um time de futebol é uma lista de jogadores de futebol? (Todas as propriedades de uma lista se aplicam a uma equipe com o mesmo significado)
    • Uma equipe é uma coleção de entidades homogêneas? Sim, a equipe é uma coleção de jogadores
    • A ordem de inclusão dos jogadores é descritiva do estado da equipe e a equipe garante que a sequência seja preservada, a menos que seja explicitamente alterada? Não e não
    • Os jogadores devem ser incluídos / descartados com base em sua posição sequencial no time? Não

Como você vê, apenas a primeira característica de uma lista é aplicável a uma equipe. Por isso, uma equipe não é uma lista. Uma lista seria um detalhe de implementação de como você gerencia sua equipe, por isso só deve ser usado para armazenar os objetos do jogador e ser manipulado com métodos da classe Team.

Neste ponto, gostaria de observar que uma classe de equipe, na minha opinião, não deve ser implementada usando uma lista; ele deve ser implementado usando uma estrutura de dados Set (HashSet, por exemplo) na maioria dos casos.





c# generics