2008-11-19 14 views
11

Mam serię metod rozszerzenie do pomocy przy zerowej sprawdzania na obiektach IDataRecord, które obecnie jestem wykonawczych tak:Czy metoda ogólna może obsługiwać zarówno typy wartości odniesienia, jak i wartości zerowalnej?

public static int? GetNullableInt32(this IDataRecord dr, int ordinal) 
{ 
    int? nullInt = null; 
    return dr.IsDBNull(ordinal) ? nullInt : dr.GetInt32(ordinal); 
} 

public static int? GetNullableInt32(this IDataRecord dr, string fieldname) 
{ 
    int ordinal = dr.GetOrdinal(fieldname); 
    return dr.GetNullableInt32(ordinal); 
} 

i tak dalej, dla każdego rodzaju muszę sobie poradzić.

Chciałbym uzupełnić je jako ogólną metodę, częściowo w celu zmniejszenia nadmiarowości, a częściowo, aby nauczyć się pisać ogólne metody w ogóle.

Pisałem to:

public static Nullable<T> GetNullable<T>(this IDataRecord dr, int ordinal) 
{ 
    Nullable<T> nullValue = null; 
    return dr.IsDBNull(ordinal) ? nullValue : (Nullable<T>) dr.GetValue(ordinal); 
} 

który działa tak długo, jak T jest typ wartości, ale jeśli T jest typem odniesienia nie będzie.

Ta metoda musiałaby zwrócić typ Nullable, jeśli T jest typem wartości, a domyślnie (T) w przeciwnym razie. Jak zaimplementować to zachowanie?

Odpowiedz

10

Wystarczy zadeklarować metodę tak:

public static T GetNullable<T>(this IDataRecord dr, int ordinal) 
{ 
    return dr.IsDBNull(ordinal) ? default(T) : (T) dr.GetValue(ordinal); 
} 

ten sposób, jeśli T jest wartością zerową int lub jakimkolwiek innym wartością zerowalną, w rzeczywistości zwróci wartość null. Jeśli jest to zwykły typ danych, po prostu zwróci wartość domyślną dla tego typu.

+2

To jest bardzo złe rozwiązanie. Jeśli jest w wartości NULL rekordu danych, nie chcesz wartości domyślnej int. NULL i ZERO są różnymi wartościami. – TcKs

+5

JEŚLI wartość w bazie danych jest zerowalna, wówczas T byłaby Nullable , w którym to przypadku NULL jest wartością domyślną, która zostanie zwrócona. – BFree

+2

Jeśli tak, dlaczego ta metoda rozszerzenia, jeśli można użyć po prostu "dr [ordinal] jako int?" ? – TcKs

-2

zrobić to w ten sposób:

DataRow record = GetSomeRecord(); 
int? someNumber = record[15] as int? 
Guid? someUID = record["MyPrimaryKey"] as Guid?; 
string someText = GetSomeText(); 
record["Description"] = someText.ToDbString(); 

// ........ 

public static class StringExtensionHelper { 
    public static object ToDbString(this string text) { 
     object ret = null != text ? text : DBNull.Value 
     return ret; 
    } 
} 

EDIT: można (lub powinny) mieć "ToDbInt32, ToDbBool, etc ..." Metody szkoleniowe dla innych typów pierwotnych oczywiście.

EDYCJA 2: Możesz także rozszerzyć klasę podstawową "obiekt" o "ToDbValue".

public static class StringExtensionHelper { 
    public static object ToDbValue(this object value) { 
     object ret = object.ReferenceEquals(value, null) ? (object)DBNull.Value : value; 
     return ret; 
    } 
} 
+0

Mogłem, ale celem tego ćwiczenia generycznego jest uniknięcie napisania osobnej metody dla każdego typu danych. I nie widzę, jak twój przykład jest nawet trafny ... Rozszerzam IDataRecord na zerowe sprawdzanie danych pobranych _z_ magazynu danych. –

+0

Jeśli chcesz sprawdzić wartości z zapisu, możesz użyć słowa kluczowego "as", które wykonasz, co chcesz. Jeśli jest w rekordzie DbNull, zostanie zwrócona wartość null. W przeciwnym razie zostanie zwrócony "int" jako "Nullable ". Nie jest wymagana żadna specjalna metoda. – TcKs

+0

Powinieneś ** nie ** rozszerzać obiektu za pomocą metody rozszerzenia, ponieważ nie będzie on dostępny we wszystkich językach, np. VB.NET –

-1

Nullable struktura jest tylko dla typów wartości, ponieważ typy referencyjne są pustych tak ...

+2

Z tego powodu nie mogę sprawić, by ten powrócił Nullable , i dlatego zadałem to pytanie. –

1

Nie sądzę, że można to zaimplementować za pomocą jednej funkcji. Jeśli C# obsługuje przeciążanie oparte na typie powrotnym, możesz być w stanie, ale nawet wtedy zaleciłbym, aby tego nie robić.

Powinieneś być w stanie osiągnąć to samo, nie używając typów danych zerowalnych i zwracając rzeczywistą wartość lub null, jako suggested przez BFree.

+0

W rzeczywistości można mieć dwie przeciążone metody, które różnią się tylko typami zwracanymi. To jest poprawna składnia: public string Method() { return ""; } public int Metoda() { return 0; } – BFree

+0

@BFree: Wpisz 'xyz' już definiuje element o nazwie 'Method' z tymi samymi typami parametrów. –

+0

@BFree: Właśnie wypróbowałem twój dokładny kod i otrzymałem następujący błąd: Wpisz "Program1" już definiuje członka o nazwie "Metoda" z tymi samymi typami parametrów. –

2

to działa:

public static T Get<T>(this IDataRecord dr, int ordinal) 
{ 
    T nullValue = default(T); 
    return dr.IsDBNull(ordinal) ? nullValue : (T) dr.GetValue(ordinal); 
} 


public void Code(params string[] args) 
{ 
    IDataRecord dr= null; 
    int? a = Get<int?>(dr, 1); 
    string b = Get<string>(dr, 2); 
} 
+1

To prawie tak samo jak odpowiedź BFree. Używanie próbki jest pomocne. –

0

Nie można zrobić z jednej metody, ale robisz to z trzech:

public static T GetData<T>(this IDataReader reader, Func<int, T> getFunc, int index) 
{ 
    if (!reader.IsClosed) 
    { 
     return getFunc(index); 
    } 
    throw new ArgumentException("Reader is closed.", "reader"); 
} 

public static T GetDataNullableRef<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : class 
{ 
    if (!reader.IsClosed) 
    { 
     return reader.IsDBNull(index) ? null : getFunc(index); 
    } 
    throw new ArgumentException("Reader is closed.", "reader"); 
} 

public static T? GetDataNullableValue<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : struct 
{ 
    if (!reader.IsClosed) 
    { 
     return reader.IsDBNull(index) ? (T?)null : getFunc(index); 
    } 
    throw new ArgumentException("Reader is closed.", "reader"); 
} 

Następnie użyć go zrobisz:

private static Whatever CreateObject(IDataReader reader) 
{ 
    Int32? id = reader.GetDataNullableValue<Int32>(reader.GetInt32, 0); 
    string name = reader.GetDataNullableRef<string>(reader.GetString, 1); 
    Int32 x = reader.GetData<Int32>(reader.GetInt32, 2); 
} 
0
public static T Get<T>(this IDataRecord rec, Func<int, T> GetValue, int ordinal) 
{ 
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(ordinal); 
} 

lub więcej wykonawca

public static T Get<T>(this IDataRecord rec, Func<IDataRecord, int, T> GetValue, int ordinal) 
{ 
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(rec, ordinal); 
} 

public static Func<IDataRecord, int, int> GetInt32 = (rec, i) => rec.GetInt32(i); 
public static Func<IDataRecord, int, bool> GetBool = (rec, i) => rec.GetBoolean(i); 
public static Func<IDataRecord, int, string> GetString = (rec, i) => rec.GetString(i); 

i używać go jak ten

rec.Get(GetString, index); 
rec.Get(GetInt32, index); 
+0

Udało Ci się zaimplementować funkcję ogólną, nie uzyskując przy tym najmniejszych korzyści. Nie trzeba pisać osobnej funkcji dla każdego typu, to _wskaźnik_produktów i powód opublikowania tego pytania. –

+0

Być może, ale różnica w stosunku do rozwiązania polega na tym, że masz tylko jedną metodę, w której sprawdzasz, czy możliwe jest odrzucanie, co może być kosztowne, jeśli masz naprawdę duże zbiory danych. – SeeR

+0

Również w porównaniu do rozwiązania BFree unikasz boksowania – SeeR

1

Nie mogę zrozumieć, dlaczego potrzeba ponad skomplikować cały ten proces. Dlaczego nie zachowywać się ładnie i prosto i używać następującego wiersza kodu:

Dla typów wartości, dla których wartość null jest poprawna, należy użyć int? iNullable = dr[ordinal] as int?;.

Dla typów wartości, w których wartość pusta jest nieprawidłowa, należy użyć int iNonNullable = dr[ordinal] as int? ?? default(int);.

Dla typów odniesienia użyj string sValue = dr[ordinal] as string;.

Dla każdego, kto myśli, że kod nie działa i że dr[ordinal] rzuci wyjątek dla DBNull, tutaj jest przykładowa metoda, która raz podana jako poprawny ciąg połączenia potwierdzi koncepcję.

private void Test() 
{ 
    int? iTestA; 
    int? iTestB; 
    int iTestC; 
    string sTestA; 
    string sTestB; 

    //Create connection 
    using (SqlConnection oConnection = new SqlConnection(@"")) 
    { 
    //Open connection 
    oConnection.Open(); 

    //Create command 
    using (SqlCommand oCommand = oConnection.CreateCommand()) 
    { 
     //Set command text 
     oCommand.CommandText = "SELECT null, 1, null, null, '1'"; 

     //Create reader 
     using (SqlDataReader oReader = oCommand.ExecuteReader()) 
     { 
     //Read the data 
     oReader.Read(); 

     //Set the values 
     iTestA = oReader[0] as int?; 
     iTestB = oReader[1] as int?; 
     iTestC = oReader[2] as int? ?? -1; 
     sTestA = oReader[3] as string; 
     sTestB = oReader[4] as string; 
     } 
    } 
    } 
} 
+0

jak już wyjaśniłem BFree w komentarzach, dr [ordinal] zgłasza wyjątek, jeśli jest to DBNull. Twój przykład nie zadziała. –

+0

@Adam Lassek - Nie wiem, dlaczego uważasz, że mój kod nie działa, czy próbowałeś? Mam go uruchomionego na systemach produkcyjnych, proszę sprawdź kod przed głosowaniem w dół. Myślę, że większość noobs może powiedzieć, że 'dr [porządek]' nie wyrzuci wyjątku, jeśli wartość jest DBNull. – stevehipwell

+0

jaka jest wersja ramowa? Kiedy pisałem to pytanie, używałem 3.5 i na pewno rzuciłem wyjątek, jeśli pole było DBNull. –

Powiązane problemy