2008-10-16 16 views
220

chcę zrobić coś takiego:Możliwy jest typ nullotyczny jako parametr ogólny?

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

zauważy pustych typu jako parametru rodzajowego.

Ponieważ funkcja GetValueOrNull mógł wrócić zerowy moja pierwsza próba była to:

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 Dostaję teraz jest:

The type 'int?' must be a reference type in order to use it as parameter 'T' in the generic type or method

Right! Nullable<int> to struct! Dlatego starałem zmianę ograniczenia klasy do struct przymusu (i jako efekt uboczny nie może wrócić null więcej):

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:

The type 'int?' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method

Czy jest możliwe określenie typu zerowego jako parametru generycznego?

+1

pls pls zrobić swój podpis 'IDataRecord' od' DbDataRecord' .. – nawfal

Odpowiedz

205

Zmień typ powrotu do Nullable i wywołać metodę z braku wartości pustych parametru

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

Proponuję użyć „columnValue == DBNull.Wartość "zamiast" jest ", ponieważ jest nieco szybsza =) – driAn

+29

Osobiste preferencje, ale możesz użyć krótkiego formularza T? Zamiast Nullable Dunc

+8

To jest dobre dla typów wartości, ale wtedy myślę, że to nie zadziała w ogóle z typami referencyjnymi (np. GetValueOrNull ), ponieważ C# nie wydaje się lubić Nullable <(ref type)> jak "string?". Rozwiązania Roberta C Barth i Jamesa Jonesa, poniżej, wydają mi się znacznie lepsze, jeśli to jest twoja potrzeba – bacar

39

prostu zrobić dwie rzeczy do oryginalnego kodu - zdjąć where ograniczenia i zmienić ostatni return od return null do return default(T) . W ten sposób możesz zwrócić wszystko, co chcesz.

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

+3

To rozwiązanie nie działa, ponieważ istnieje logiczna różnica między wartościami NULL i 0 –

+11

Działa, jeśli typ, który przekazuje, to int ?. Zwróci NULL, tak jak chce. Jeśli przekaże int jako typ, zwróci 0, ponieważ int nie może mieć wartości NULL. Poza tym, że go wypróbowałem i działa idealnie. –

+0

Jest to najbardziej poprawna i elastyczna odpowiedź. Jednak "return default" jest wystarczający (nie potrzebujesz '(T)', kompilator wywnioskuje go z typu zwracanego przez podpis). – McGuireV10

4

Po prostu musiałem zrobić coś niesamowitego podobnego 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; 
} 
91
public static T GetValueOrDefault<T>(this IDataRecord rdr, int index) 
{ 
    object val = rdr[index]; 

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

    return default(T); 
} 

Wystarczy użyć go tak:

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

Można to skrócić do: return rdr.IsDBNull (index)? default (T): (T) rdr [indeks]; – Foole

+6

Myślę, że to pytanie wyraźnie chce _null_, a nie _default (T) _. – mafu

+1

@mafu default (T) zwróci wartość null dla typów referencyjnych, a 0 dla typów liczbowych, dzięki czemu rozwiązanie będzie bardziej elastyczne. –

3

myślę chcesz obsługiwać typy odwołań i struct typy. Używam go do konwertowania ciągów elementów XML na bardziej typ maszynowy. Możesz usunąć obiekt nullAlternative z odbiciem. Narzędzie do formatowania obsługuje "zależność" od kultury. lub "," separator w np. miejsca dziesiętne lub int i double. to może 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żna go używać tak:

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); 
4

Zastrzeżenie: Ta odpowiedź działa, ale jest przeznaczony wyłącznie do celów edukacyjnych. :) Rozwiązanie Jamesa Jonesa jest prawdopodobnie najlepsze i na pewno takie, z którym chciałbym pójść.

C# 4.0 w dynamic słów kluczowych sprawia, że ​​to jeszcze łatwiejsze, jeśli mniej bezpieczne:

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

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

Teraz nie trzeba wyraźnej typów parametrów na RHS:

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

w rzeczywistości, w ogóle go nie potrzebujesz!

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

value będzie teraz int, lub ciąg, czy cokolwiek zostało zwrócone z DB typ.

Jedynym problemem jest to, że to nie przeszkadza w używaniu non-zerowalne typów na LHS, w takim przypadku dostaniesz raczej przykry wyjątek czasu wykonywania takiego:

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

jak w przypadku wszystkich kodu używa dynamic: kodera zastrzeżenia.

2

To może być martwy wątek, ale mają tendencję do korzystania z następujących czynności:

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName) 
where T : struct 
{ 
    return reader[columnName] as T?; 
} 
+1

"Typ" T "musi być typem nie podlegającym zerowaniu, aby można go było użyć jako parametru" T "w typowym rodzaju lub metodzie" Nullable "" –

1

wiem, że to jest stary, ale tutaj jest 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 don” t care if T była wartością lub typem referencji. Tylko jeśli funkcja zwróci wartość true, masz rozsądną wartość z bazy danych. Zastosowanie:

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

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

+0

Byłoby dobrze, gdybyś pracował nad konwencjami nazewnictwa. Brakuje im spójności. W jednym miejscu istnieje zmienna bez kapitału, a tam jest jedna. To samo z parametrami dla metod. –

+0

Gotowe i gotowe! Kod nadziei wygląda teraz lepiej. Bob to twoja ciocia :) Wszystko jest skookum – nurchi

1

prostu napotkał ten sam problem sam.

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

Działa z dowolnym typem bez problemu. Jeśli wynikiem jest DBNull, zwraca wartość null, ponieważ konwersja nie powiedzie się.

+0

W rzeczywistości prawdopodobnie możesz zrobić "int v = reader [" myYear "] ?? - 1;" lub inne domyślne zamiast "-1". Może to jednak powodować problemy, jeśli wartość to 'DBNull' ... – nurchi

0

Wiele ograniczeń ogólnych nie może być łączonych w sposób OR (mniej restrykcyjny), tylko w trybie ORAZ (bardziej restrykcyjnym). Oznacza to, że jedna metoda nie może obsłużyć obu scenariuszy. Ogólne ograniczenia również nie mogą być użyte do stworzenia unikalnego podpisu dla metody, więc musisz użyć dwóch 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ść zerową, a nigdy domyślną wartość wszystkich możliwych typów wartości. GetValueOrDefault = zły. GetValueOrNull = dobrze.

Użyłem słów "Null" i "Nullable", aby rozróżnić typy odniesienia i typy wartości. A oto przykład kilku metod rozszerzeń, które napisałem komplementując metodę FirstOrDefault w klasie System.Linq.Elumerable.

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; 
    } 
Powiązane problemy