Frage

Ich habe eine generische Methode wie folgt definiert:

public void MyMethod<T>(T myArgument)

Das erste, was ich tun möchte, ist zu überprüfen, ob der Wert von myArgument der Standardwert für diesen Typ ist, so etwas wie folgt aus:

if (myArgument == default(T))

Aber das ist nicht kompilieren, weil ich nicht garantiert, dass T den Operator == umsetzt. Also wechselte ich den Code so aus:

if (myArgument.Equals(default(T)))

Nun stellt dies jedoch fehl, wenn myArgument null ist, welcher Teil von dem, was für Ich teste. Ich kann eine explizite NULL-Prüfung wie folgt hinzu:

if (myArgument == null || myArgument.Equals(default(T)))

Jetzt fühlt dies überflüssig zu mir. ReSharper schlägt auch, dass ich die myArgument ändern == null Teil in myArgument == default (T), das ist, wo ich angefangen habe. Gibt es einen besseren Weg, um dieses Problem zu lösen?

Ich brauche Unterstützung beide verweist Typen und Werttypen.

War es hilfreich?

Lösung

Zur Vermeidung von Boxen, die beste Möglichkeit, Generika vergleichen für die Gleichstellung mit EqualityComparer<T>.Default ist. Dies steht im Einklang IEquatable<T> (ohne Boxen) sowie object.Equals, und kümmert sich um alle die Nullable<T> „angehoben“ Nuancen. Daraus folgt:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}

Dies wird übereinstimmen:

  • null für Klassen
  • null (leer) für Nullable<T>
  • null / false / etc für andere structs

Andere Tipps

Wie wäre es damit:

if (object.Equals(myArgument, default(T)))
{
    //...
}

die static object.Equals() Methode verwenden vermeidet die Notwendigkeit für Sie die null überprüfen selbst zu tun. Explizit Qualifikation wohl das Gespräch mit object. auf Ihrem Kontext nicht notwendig ist, abhängig, aber ich normalerweise Präfix static Anrufe nur mit dem Typnamen des Code mehr löslich ist.

zu machen

Ich konnte eine Microsoft Connect Artikel , dass dieses Thema im Detail diskutiert:

  

Leider ist dieses Verhalten von Entwurf und es gibt keine einfache Lösung Verwendung von mit Typ-Parametern zu ermöglichen, die Werttypen enthalten kann.

     

Wenn die Typen sind bekannt Referenztypen sein, die Standard-Überlastung definiert auf Objekt Tests Variablen als Referenz Gleichheit, obwohl eine Art eigene benutzerdefinierte Überlastung angeben. Der Compiler bestimmt, welche basierend verwenden Überlastung des statischen Typ der Variablen (die Bestimmung ist nicht polymorph). Deshalb, wenn Sie Ihr Beispiel ändern, um den generischen Typparameter T auf einen nicht versiegelten Referenztyp (wie Exception) zu beschränken, kann der Compiler die spezifische Überlastung bestimmen zu verwenden, und der folgende Code kompilieren:

public class Test<T> where T : Exception
  

Wenn die Typen bekannt sind Werttypen zu sein, führt bestimmten Wert Gleichheitstests auf der Basis der genauen Typen verwendet. Es ist nicht gut „default“ Vergleich hier seit Referenzvergleiche auf Werttypen nicht sinnvoll sind und die Compiler können nicht wissen, welcher Vergleich bestimmten Wertes zu emittieren. Der Compiler kann einen Aufruf an ValueType.Equals (Object) emittiert aber diese Methode verwendet Reflektion und ist ziemlich ineffizient im Vergleich zu den spezifischen Wert Vergleiche. Deshalb, auch wenn Sie eine Wert-Typ-Einschränkung auf T angeben waren, gibt es nichts vernünftig für die Compiler zu erzeugen hier:

public class Test<T> where T : struct
  

Im Fall, dass Sie präsentiert, wo der Compiler nicht einmal weiß, ob T ein Wert oder Referenztyp ist, gibt es in ähnlicher Weise nichts zu erzeugen, dass für alle möglichen Arten gültig wäre. Ein Referenz Vergleich würde für Werttypen nicht gültig sein und eine Art von Wert-Vergleich würde für Referenztypen unerwartet sein, die nicht überlasten.

Hier ist, was Sie tun können ...

Ich habe bestätigt, dass beide Methoden für einen allgemeinen Vergleich von Referenz- und Werttypen arbeiten:

object.Equals(param, default(T))

oder

EqualityComparer<T>.Default.Equals(param, default(T))

Gehen Sie Vergleiche mit dem „==“ Operator müssen Sie eine dieser Methoden verwenden:

Wenn alle Fälle von T aus einer bekannten Basisklasse ableiten können Sie die Compiler lassen generische Typen Einschränkungen wissen verwenden.

public void MyMethod<T>(T myArgument) where T : MyBase

Der Compiler erkennt dann, wie Operationen auf MyBase ausführen und wird die „Operator‚==‘kann nicht auf Operanden vom Typ‚T‘angewandt werden, und‚T‘“ nicht werfen Fehler, den Sie jetzt sehen.

Eine andere Möglichkeit wäre T auf jede Art zu beschränken, die IComparable implementiert.

public void MyMethod<T>(T myArgument) where T : IComparable

Und dann die CompareTo Methode durch die IComparable-Schnittstelle .

Versuchen Sie folgendes:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))

das sollte kompilieren, und das tun, was Sie wollen.

(Edited)

Marc GRA hat die beste Antwort, aber ich wollte einen einfachen Code-Schnipsel Ich arbeitete schreiben, um es zu demonstrieren. laufen diese nur in einer einfachen C # Konsole App:

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null)); //True //True

    Console.ReadKey();
}

Eine weitere Sache: kann jemand mit VS2008 versuchen, dies als eine Erweiterung Methode? Ich bin mit 2005 hier fest, und ich bin gespannt, ob das erlaubt würde.


Edit: Hier ist, wie es als Erweiterung Methode arbeiten lassen:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // value type
        Console.WriteLine(1.IsDefault());
        Console.WriteLine(0.IsDefault());

        // reference type
        Console.WriteLine("test".IsDefault());
        // null must be cast to a type
        Console.WriteLine(((String)null).IsDefault());
    }
}

// The type cannot be generic
public static class TypeHelper
{
    // I made the method generic instead
    public static bool IsDefault<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}

Um alle Arten von T-Griff, auch dann, wenn T ein primitiver Typ ist, müssen Sie in den beiden Vergleichsmethoden kompilieren:

    T Get<T>(Func<T> createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }

Es wird ein Problem, hier zu sein -

Wenn Sie vorhaben, dies zu ermöglichen, für jede Art zu arbeiten, default (T) wird für Referenztypen immer null sein, und 0 (oder Struktur voller 0) für Werttypen.

Dies ist wahrscheinlich nicht das Verhalten Sie nach, though. Wenn Sie dies in allgemeiner Weise arbeiten wollen, müssen Sie wahrscheinlich Reflexion verwenden, um die Art von T zu überprüfen und Werttypen Typen anders als Referenz zu behandeln.

Alternativ können Sie eine Schnittstelle Beschränkung auf diese setzen, und die Schnittstelle eine Art und Weise gegen den Ausfall der Klasse / Struktur zu überprüfen bieten könnte.

Ich glaube, Sie wahrscheinlich diese Logik in zwei Teile spalten müssen und zunächst für null überprüfen.

public static bool IsNullOrEmpty<T>(T value)
{
    if (IsNull(value))
    {
        return true;
    }
    if (value is string)
    {
        return string.IsNullOrEmpty(value as string);
    }
    return value.Equals(default(T));
}

public static bool IsNull<T>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

In der IsNull-Methode, sind wir auf die Tatsache stützen, dass Valuetype Objekte per definitionem nicht null sein kann, so, wenn der Wert passiert, eine Klasse zu sein, die von Valuetype leitet, wissen wir bereits, es nicht null ist. Auf der anderen Seite, wenn es nicht ein Werttyp ist, dann können wir vergleichen nur Wert Guss auf ein Objekt gegen null. Wir konnten die Prüfung gegen Valuetype vermeiden, indem sie zu einem Guss gehe direkt zu widersprechen, aber das würde bedeuten, dass ein Werttyp boxed erhalten würde, was etwas ist, wir wollen wahrscheinlich zu vermeiden, da es bedeutet, dass ein neues Objekt auf dem Heap erstellt wird.

In der IsNullOrEmpty Methode sind wir für den speziellen Fall einer Zeichenfolge zu überprüfen. Für alle anderen Typen, sind zu vergleichen wir den Wert (die bereits wissen, ist nicht null) dagegen Standardwert ist die für alle Referenztypen null und für Werttypen ist in der Regel irgendeine Form von Null (wenn sie ist Integral).

Die Verwendung dieser Methoden, der folgende Code verhält sich wie Sie vielleicht erwarten:

class Program
{
    public class MyClass
    {
        public string MyString { get; set; }
    }

    static void Main()
    {
        int  i1 = 1;    Test("i1", i1); // False
        int  i2 = 0;    Test("i2", i2); // True
        int? i3 = 2;    Test("i3", i3); // False
        int? i4 = null; Test("i4", i4); // True

        Console.WriteLine();

        string s1 = "hello";      Test("s1", s1); // False
        string s2 = null;         Test("s2", s2); // True
        string s3 = string.Empty; Test("s3", s3); // True
        string s4 = "";           Test("s4", s4); // True

        Console.WriteLine();

        MyClass mc1 = new MyClass(); Test("mc1", mc1); // False
        MyClass mc2 = null;          Test("mc2", mc2); // True
    }

    public static void Test<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}

ich benutze:

public class MyClass<T>
{
  private bool IsNull() 
  {
    var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
    return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
  }
}

Sie wissen nicht, ob dies mit Ihren Anforderungen funktioniert oder nicht, aber man konnte T einschränken ein Typ zu sein, der eine Schnittstelle wie IComparable implementiert und verwenden Sie dann den ComparesTo () -Methode von dieser Schnittstelle (das IIRC unterstützt / Griffe nulls ) wie folgt aus:

public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))

Es gibt wahrscheinlich andere Schnittstellen, die Sie auch IEquitable nutzen könnten, etc.

@ilitirit:

public class Class<T> where T : IComparable
{
    public T Value { get; set; }
    public void MyMethod(T val)
    {
        if (Value == val)
            return;
    }
}

Operator '==' kann nicht auf Operanden vom Typ 'T' und 'T'

angewandt werden

Ich kann nicht einen Weg finde dies unter Berufung auf die Equals-Methode oder object.Equals gefolgt ohne ausdrücklichen Nulltest zu tun, wie oben vorgeschlagen.

können Sie eine Lösung entwickeln System.Comparison verwenden, aber das wird am Ende mit viel mehr Zeilen Code und erhöht die Komplexität wirklich wesentlich.

Ich glaube, Sie waren in der Nähe.

if (myArgument.Equals(default(T)))

Nun stellt dies jedoch fehl, wenn myArgument null ist, welcher Teil von dem, was für Ich teste. Ich kann eine explizite NULL-Prüfung wie folgt hinzu:

Sie müssen nur das Objekt umgekehrt, auf das die Gleichen für einen eleganten null sicheren Ansatz genannt wird.

default(T).Equals(myArgument);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top