Czy dopuszcza się typ zerowy jako parametr ogólny?

288

Chcę zrobić coś takiego:

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

Zwróć uwagę na typ zerowalny jako parametr ogólny.

Ponieważ GetValueOrNullfunkcja może zwrócić null, moja pierwsza próba była następująca:

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

Ale błąd, który teraz otrzymuję, to:

Wpisz „int?” musi być typem odniesienia, aby można go było używać jako parametru „T” w typie lub metodzie ogólnej

Dobrze! Nullable<int>jest struct! Próbowałem więc zmienić ograniczenie klasy na structograniczenie (i jako efekt uboczny nie mogę już nullwięcej powrócić ):

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

Teraz zadanie:

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

Daje następujący błąd:

Wpisz „int?” musi być niedopuszczalnym typem wartości, aby użyć go jako parametru „T” w typie lub metodzie ogólnej

Czy w ogóle możliwe jest podanie typu zerowalnego jako parametru ogólnego?

Tom Pester
źródło
3
Prosimy o złożenie podpisu IDataRecordod DbDataRecord...
nawfal

Odpowiedzi:

262

Zmień typ zwracany na Nullable i wywołaj metodę z parametrem innym niż nullable

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
źródło
1
Sugeruję użycie „columnValue == DBNull.Value” zamiast operatora „is”, ponieważ jest on nieco szybszy =)
driAn
40
Osobiste preferencje, ale możesz użyć krótkiego formularza T? zamiast Nullable <T>
Dunc
11
Jest to w porządku dla typów wartości, ale myślę, że w ogóle nie będzie działać z typami referencji (np. GetValueOrNull <łańcuch>), ponieważ C # nie wydaje się lubić Nullable <(typ ref)> jak „string?”. Rozwiązania Roberta C Barth i Jamesa Jonesa poniżej wydają mi się znacznie lepsze, jeśli taka jest twoja potrzeba.
Bacar
2
@bacar - racja, stąd „gdzie T: struct”, jeśli chcesz typy referencyjne, możesz stworzyć podobną metodę z „where T: class”
Greg Dean
4
@Greg - jasne, ale potrzebujesz drugiej metody i nie możesz przeciążyć nazwy. Jak mówię, jeśli chcesz obsługiwać zarówno typy val, jak i ref, myślę, że na tej stronie przedstawiono bardziej przejrzyste rozwiązania.
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);
}

Po prostu użyj tego w ten sposób:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);
James Jones
źródło
6
Można to skrócić do: return rdr.IsDBNull (index)? default (T): (T) rdr [indeks];
Foole,
11
Myślę, że to pytanie wyraźnie chce mieć wartość zerową , a nie domyślną (T) .
mafu
5
@mafu default (T) zwróci null dla typów referencyjnych, a 0 dla typów numerycznych, dzięki czemu rozwiązanie będzie bardziej elastyczne.
James Jones
2
Myślę, że to jaśniejsze albo nazywają to GetValueOrDefaultwyjaśnić, że zwraca default(T)zamiast null. Alternatywnie możesz zgłosić wyjątek, jeśli Tnie ma wartości zerowej.
Sam
Ta metoda ma wiele zalet i zmusza do myślenia o zwrocie również wartości innej niż zero.
Shane
61

Po prostu zrób dwie rzeczy z oryginalnym kodem - usuń whereograniczenie i zmień ostatnią returnz return nullna return default(T). W ten sposób możesz zwrócić dowolny typ.

Nawiasem mówiąc, możesz uniknąć używania is, zmieniając swoje ifoświadczenie na if (columnValue != DBNull.Value).

Robert C. Barth
źródło
4
To rozwiązanie nie działa, ponieważ istnieje logiczna różnica między wartością NULL a 0
Greg Dean
15
Działa, jeśli typ, który przechodzi, to int ?. Zwróci NULL, tak jak chce. Jeśli poda int jako typ, zwróci 0, ponieważ int nie może być NULL. Poza tym, że go wypróbowałem i działa idealnie.
Robert C. Barth
2
To jest najbardziej poprawna i elastyczna odpowiedź. Jest jednak return defaultwystarczający (nie potrzebujesz (T), kompilator wywnioskuje to na podstawie typu zwrotu podpisu).
McGuireV10,
5

Oświadczenie: Ta odpowiedź działa, ale jest przeznaczona wyłącznie do celów edukacyjnych. :) Rozwiązanie Jamesa Jonesa jest prawdopodobnie najlepsze tutaj i na pewno takie, z którym bym poszedł.

Słowo dynamickluczowe w C # 4.0 czyni to jeszcze łatwiejszym, jeśli mniej bezpiecznym:

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

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

Teraz nie potrzebujesz wyraźnego podpowiedzi typu na RHS:

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

W rzeczywistości nawet go nie potrzebujesz!

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

value będzie teraz liczbą całkowitą, ciągiem znaków lub dowolnym typem zwróconym z bazy danych.

Jedynym problemem jest to, że nie przeszkadza to w używaniu typów nie dopuszczających wartości zerowej w LHS, w którym to przypadku otrzymasz dość nieprzyjemny wyjątek czasu wykonywania, taki jak:

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

Podobnie jak w przypadku wszystkich kodów, które używają dynamic: kodera zastrzeżeń.

Ian Kemp
źródło
4

Musiałem zrobić coś niesamowitego do tego. Mój kod:

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
źródło
3

Myślę, że chcesz obsługiwać typy referencyjne i typy struktur. Używam go do konwersji ciągów elementów XML na bardziej typowe typy. Możesz usunąć nullAlternative z odbiciem. Dostawca formatu ma obsługiwać zależne od kultury „.” lub separator „,” np. ułamki dziesiętne lub liczby całkowite i podwójne. Może to działać:

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

Możesz użyć tego w następujący sposób:

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
źródło
2

To może być martwy wątek, ale zwykle używam następujących elementów:

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct 
{
    return reader[columnName] as T?;
}
Ryan Horch
źródło
1
„Typ„ T ”musi być wartością niedopuszczalną, aby można go było użyć jako parametru„ T ”w typie ogólnym lub metodzie„ Nullable <T> ””
Ian Warburton,
1

Właśnie sam spotkałem ten sam problem.

... = reader["myYear"] as int?; działa i jest czysty.

Działa z każdym rodzajem bez problemu. Jeśli wynikiem jest DBNull, zwraca wartość null, ponieważ konwersja nie powiodła się.

Hele
źródło
W rzeczywistości prawdopodobnie możesz to zrobić int v=reader["myYear"]??-1;lub użyć innej domyślnej opcji -1. Może to jednak powodować problemy, jeśli wartość wynosi DBNull...
nurchi
1

Wiem, że to stare, ale oto inne rozwiązanie:

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

Teraz nie obchodzi cię, czy Tbył to typ wartości czy referencyjny. Tylko jeśli funkcja zwróci true, masz rozsądną wartość z bazy danych. Stosowanie:

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

To podejście jest bardzo podobne do int.TryParse("123", out MyInt);

Nurchi
źródło
Byłoby dobrze, gdybyś pracował nad konwencjami nazewnictwa. Brakuje im spójności. W jednym miejscu jest zmienna bez kapitału, a następnie jedna z. To samo dotyczy parametrów metod.
Marino Šimić
1
Gotowe i gotowe! Kod nadziei wygląda teraz lepiej. Bob jest twoją ciocią :) Wszystko skończyło się
nurchi
0

Wiele ogólnych ograniczeń nie może być łączonych w sposób OR (mniej restrykcyjny), tylko w sposób AND (bardziej restrykcyjny). Oznacza to, że jedna metoda nie obsługuje obu scenariuszy. Ograniczeń ogólnych nie można również użyć do utworzenia unikalnego podpisu dla metody, dlatego trzeba będzie użyć 2 oddzielnych nazw metod.

Można jednak użyć ogólnych ograniczeń, aby upewnić się, że metody są używane poprawnie.

W moim przypadku chciałem zwrócić wartość null, a nigdy domyślną wartość dowolnego możliwego typu wartości. GetValueOrDefault = zły. GetValueOrNull = dobra.

Użyłem słów „Null” i „Nullable”, aby rozróżnić typy referencyjne i typy wartości. A oto przykład kilku metod rozszerzenia, które napisałem, które uzupełniają metodę FirstOrDefault w klasie 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
źródło
0

Krótsza droga:

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

powrót 0do int, a nulldlaint?

Amirhossein Yari
źródło