Nullable Typ als generischer Parameter möglich?

287

Ich möchte so etwas machen:

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

Beachten Sie den nullbaren Typ als generischen Parameter.

Da die GetValueOrNullFunktion null zurückgeben konnte, war mein erster Versuch folgender:

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

Aber der Fehler, den ich jetzt bekomme, ist:

Der Typ 'int?' muss ein Referenztyp sein, um ihn als Parameter 'T' im generischen Typ oder in der generischen Methode verwenden zu können

Richtig! Nullable<int>ist ein struct! Also habe ich versucht, die Klasseneinschränkung in eine structEinschränkung zu ändern (und als Nebeneffekt kann ich nicht nullmehr zurückkehren):

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

Nun die Aufgabe:

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

Gibt den folgenden Fehler aus:

Der Typ 'int?' muss ein nicht nullbarer Werttyp sein, um ihn als Parameter 'T' im generischen Typ oder in der generischen Methode zu verwenden

Ist es überhaupt möglich, einen nullbaren Typ als generischen Parameter anzugeben?

Tom Pester
quelle
3
Bitte machen Sie Ihre Unterschrift IDataRecordvon DbDataRecord..
Nawfal

Antworten:

262

Ändern Sie den Rückgabetyp in Nullable und rufen Sie die Methode mit dem Parameter nicht nullable auf

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
quelle
1
Ich schlage vor, Sie verwenden "columnValue == DBNull.Value" anstelle des Operators 'is', da dieser etwas schneller ist =)
driAn
40
Persönliche Präferenz, aber Sie können die Kurzform T verwenden? anstelle von Nullable <T>
Dunc
11
Dies ist für Werttypen in Ordnung, aber ich denke, dass es mit Referenztypen (z. B. GetValueOrNull <string>) überhaupt nicht funktioniert, da C # Nullable <(ref type)> wie "string?" Nicht zu mögen scheint. Die folgenden Lösungen von Robert C. Barth & James Jones scheinen mir viel besser zu sein, wenn Sie dies benötigen.
Bacar
2
@bacar - richtig, daher das "where T: struct". Wenn Sie Referenztypen möchten, können Sie eine ähnliche Methode mit "where T: class" erstellen
Greg Dean
4
@ Greg - sicher, aber dann brauchst du eine zweite Methode und kannst den Namen nicht überladen. Wie gesagt, wenn Sie sowohl mit val- als auch mit ref-Typen umgehen möchten, werden auf dieser Seite meiner Meinung nach sauberere Lösungen vorgestellt.
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);
}

Verwenden Sie es einfach so:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);
James Jones
quelle
6
Dies könnte verkürzt werden zu: return rdr.IsDBNull (index)? Standard (T): (T) rdr [Index];
Narr
11
Ich denke, diese Frage möchte explizit null , nicht default (T) .
Mafu
5
@mafu default (T) gibt für Referenztypen null und für numerische Typen 0 zurück, wodurch die Lösung flexibler wird.
James Jones
2
Ich denke, es ist klarer, dies entweder zu nennen GetValueOrDefault, um zu verdeutlichen, dass es default(T)eher zurückkehrt als null. Alternativ können Sie eine Ausnahme Tauslösen lassen, wenn diese nicht nullwertfähig ist.
Sam
Diese Methode hat viele Vorteile und zwingt Sie dazu, darüber nachzudenken, auch etwas anderes als null zurückzugeben.
Shane
61

Machen Sie einfach zwei Dinge mit Ihrem ursprünglichen Code - entfernen Sie die whereEinschränkung und ändern Sie die letzte returnvon return nullin return default(T). Auf diese Weise können Sie einen beliebigen Typ zurückgeben.

Übrigens können Sie die Verwendung von vermeiden, isindem Sie Ihre ifAussage in ändern if (columnValue != DBNull.Value).

Robert C. Barth
quelle
4
Diese Lösung funktioniert nicht, da es einen logischen Unterschied zwischen NULL und 0 gibt
Greg Dean
15
Es funktioniert, wenn der Typ, den er übergibt, int? Ist. Es wird NULL zurückgeben, genau wie er es will. Wenn er int als Typ übergibt, gibt er 0 zurück, da ein int nicht NULL sein kann. Neben der Tatsache, dass ich es ausprobiert habe und es perfekt funktioniert.
Robert C. Barth
2
Dies ist die richtigste und flexibelste Antwort. Dies return defaultist jedoch ausreichend (Sie benötigen das nicht (T), der Compiler leitet es aus dem Signaturrückgabetyp ab).
McGuireV10
5

Haftungsausschluss: Diese Antwort funktioniert, ist jedoch nur für Bildungszwecke gedacht. :) James Jones 'Lösung ist wahrscheinlich die beste hier und sicherlich die, mit der ich gehen würde.

Das dynamicSchlüsselwort von C # 4.0 macht dies noch einfacher, wenn auch weniger sicher:

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

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

Jetzt brauchen Sie keinen expliziten Typ, der auf der RHS angibt:

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

Tatsächlich brauchen Sie es überhaupt nicht!

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

value wird nun ein int oder eine Zeichenfolge oder ein beliebiger Typ sein, der von der Datenbank zurückgegeben wurde.

Das einzige Problem ist, dass dies Sie nicht daran hindert, nicht nullfähige Typen auf der LHS zu verwenden. In diesem Fall erhalten Sie eine ziemlich unangenehme Laufzeitausnahme wie:

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

Wie bei jedem Code, der dynamicFolgendes verwendet : Vorbehaltscodierer.

Ian Kemp
quelle
4

Musste einfach etwas Unglaubliches ähnlich machen. Mein Code:

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
quelle
3

Ich denke, Sie möchten Referenztypen und Strukturtypen behandeln. Ich verwende es, um XML-Element-Zeichenfolgen in einen typisierteren Typ zu konvertieren. Sie können die nullAlternative mit Reflektion entfernen. Der Formatanbieter soll das kulturabhängige '.' oder ',' Trennzeichen in z. B. Dezimalstellen oder Ints und Doubles. Dies kann funktionieren:

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

Sie können es so verwenden:

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
quelle
2

Dies mag ein toter Thread sein, aber ich neige dazu, Folgendes zu verwenden:

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct 
{
    return reader[columnName] as T?;
}
Ryan Horch
quelle
1
"Der Typ 'T' muss ein nicht nullbarer Werttyp sein, um ihn als Parameter 'T' im generischen Typ oder der generischen Methode 'Nullable <T>' zu verwenden"
Ian Warburton
1

Ich bin gerade selbst auf das gleiche Problem gestoßen.

... = reader["myYear"] as int?; funktioniert und ist sauber.

Es funktioniert mit jedem Typ ohne Probleme. Wenn das Ergebnis DBNull ist, wird null zurückgegeben, da die Konvertierung fehlschlägt.

Hele
quelle
In der Tat könnten Sie wahrscheinlich int v=reader["myYear"]??-1;oder einen anderen Standard anstelle von tun -1. Dies kann jedoch zu Problemen führen, wenn der Wert DBNull...
Nurchi
1

Ich weiß, dass dies alt ist, aber hier ist eine andere Lösung:

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

Jetzt ist es Ihnen egal, ob Tes sich um einen Wert oder einen Referenztyp handelt. Nur wenn die Funktion true zurückgibt, haben Sie einen angemessenen Wert aus der Datenbank. Verwendung:

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

Dieser Ansatz ist sehr ähnlich zu int.TryParse("123", out MyInt);

Nurchi
quelle
Es wäre gut, wenn Sie an Ihren Namenskonventionen arbeiten würden. Ihnen fehlt die Konsistenz. An einem Ort gibt es eine Variable ohne Kapital, dann gibt es eine mit. Gleiches gilt für Parameter zu den Methoden.
Marino Šimić
1
Gemacht und gemacht! Hoffe, Code sieht jetzt besser aus. Bob ist deine Tante :) Alles ist
Skookum
0

Mehrere generische Einschränkungen können nicht im ODER (weniger restriktiv), sondern nur im UND (restriktiver) kombiniert werden. Dies bedeutet, dass eine Methode nicht beide Szenarien verarbeiten kann. Die generischen Einschränkungen können auch nicht verwendet werden, um eine eindeutige Signatur für die Methode zu erstellen. Daher müssten Sie zwei separate Methodennamen verwenden.

Sie können jedoch die generischen Einschränkungen verwenden, um sicherzustellen, dass die Methoden korrekt verwendet werden.

In meinem Fall wollte ich speziell, dass null zurückgegeben wird und niemals der Standardwert eines möglichen Werttyps. GetValueOrDefault = schlecht. GetValueOrNull = gut.

Ich habe die Wörter "Null" und "Nullable" verwendet, um zwischen Referenztypen und Werttypen zu unterscheiden. Und hier ist ein Beispiel für einige Erweiterungsmethoden, die ich geschrieben habe und die die FirstOrDefault-Methode in der System.Linq.Enumerable-Klasse ergänzen.

    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
quelle
0

Der kürzere Weg:

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

Rückkehr 0für intund nullfürint?

Amirhossein Yari
quelle