it-swarm-eu.dev

Null nebo výchozí porovnání obecného argumentu v jazyce C #

Mám definovanou obecnou metodu takto:

public void MyMethod<T>(T myArgument)

První věc, kterou chci udělat, je zkontrolovat, zda je hodnota parametru myArgument výchozí hodnotou pro tento typ, něco takového:

if (myArgument == default(T))

Ale to není kompilovat, protože jsem nezaručil, že T bude implementovat operátora ==. Tak jsem kód přepnul na toto:

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

Teď to kompiluje, ale nezdaří, pokud je myArgument null, což je součástí toho, co testuji. Můžu přidat explicitní nulovou kontrolu takto:

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

Teď mi to připadá zbytečné. ReSharper dokonce naznačuje, že změním část myArgument == null na myArgument == default (T), kde jsem začal. Existuje lepší způsob, jak tento problém vyřešit?

Musím to podpořit oba odkazy na typy a typy hodnot.

239
Stefan Moser

Chcete-li se vyhnout boxu, nejlepší způsob, jak porovnat generika pro rovnost, je EqualityComparer<T>.Default. Toto respektuje IEquatable<T> (bez boxu) stejně jako object.Equals a zpracovává všechny Nullable<T> "zvednuté" nuance. Proto:

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

To bude odpovídat:

  • null pro třídy
  • null (prázdný) pro Nullable<T>
  • nula/false/etc pro jiné struktury
486
Marc Gravell

Jak na to:

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

Použitím metody static object.Equals() se vyhnete nutnosti kontrolovat null sami. Výslovná kvalifikace volání pomocí object. pravděpodobně není nutná v závislosti na vašem kontextu, ale obvykle předvolávám static hovory s názvem typu, aby byl kód rozpustnější.

113
Kent Boogaart

Byl jsem schopen najít článek Microsoft Connect který tento problém podrobně popisuje:

Toto chování je bohužel záměrné a není snadné řešení umožňující použití s ​​parametry typu, které mohou obsahovat typy hodnot.

Jsou-li tyto typy označovány jako referenční typy, je výchozí přetížení definováno na proměnných testů objektů pro referenční rovnost, ačkoli typ může specifikovat vlastní vlastní přetížení. Kompilátor určuje, které přetížení se má použít na základě statického typu proměnné (stanovení není polymorfní). Pokud tedy změníte příklad tak, aby byl parametr obecného typu T omezen na neuzavřený typ reference (například výjimka), může kompilátor určit konkrétní přetížení, které se má použít, a následující kód by zkompiloval:

public class Test<T> where T : Exception

Pokud je známo, že typy jsou hodnotové typy, provádí specifické testy rovnosti na základě použitých typů. Neexistuje žádné dobré "výchozí" srovnání, protože referenční srovnání nejsou smysluplné pro typy hodnot a kompilátor nemůže vědět, které srovnání konkrétní hodnoty vyzařuje. Kompilátor by mohl vydat volání ValueType.Equals (Object), ale tato metoda používá odraz a je poměrně neefektivní ve srovnání s porovnáním specifických hodnot. Proto i když jste zadali omezení typu hodnoty na T, neexistuje nic rozumného, ​​aby kompilátor generoval zde:

public class Test<T> where T : struct

V případě, kdy jste prezentovali, kde kompilátor ani neví, zda T je hodnota nebo referenční typ, není nic podobného, ​​co by bylo platné pro všechny možné typy. Porovnání referencí by nebylo platné pro typy hodnot a některé porovnávání hodnot by bylo neočekávané u referenčních typů, které nejsou přetíženy.

Zde je to, co můžete udělat ...

Ověřil jsem, že obě tyto metody pracují pro obecné porovnání referenčních a hodnotových typů:

object.Equals(param, default(T))

nebo

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

Pro porovnání s operátorem "==" budete muset použít jednu z těchto metod:

Pokud všechny případy T pocházejí ze známé základní třídy, můžete kompilátorovi umožnit, aby věděl, že používá obecná omezení typu.

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

Kompilátor pak rozpozná, jak provádět operace na MyBase a nebude házet "Operátor" == 'nelze použít pro operandy typu' T 'a' T '"chyby, které vidíte nyní.

Další možností by bylo omezit T na jakýkoliv typ, který implementuje IComparable.

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

A pak použijte metodu CompareTo definovanou rozhraním IComparable .

24
Eric Schoonover

Zkuste to:

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

které by měly kompilovat a dělat, co chcete.

(Upraveno)

Marc Gravell má nejlepší odpověď, ale chtěl jsem napsat jednoduchý úryvek kódu, který jsem pracoval, abych to dokázal. Stačí to spustit v jednoduché aplikaci konzoly C #:

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

Ještě jedna věc: může někdo s VS2008 to zkusit jako metodu rozšíření? Jsem tu s 2005 a jsem zvědavý, jestli to bude dovoleno.


Edit: Zde je návod, jak to fungovat jako metodu rozšíření:

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));
    }
}
7
Joel Coehoorn

Ke zpracování všech typů T, včetně toho, kde T je primitivní typ, budete muset kompilovat obě metody porovnání:

    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;
    }
6
Nick Farina

Bude tu problém -

Pokud to chcete povolit pro libovolný typ, výchozí (T) bude vždy null pro referenční typy a 0 (nebo struct full of 0) pro typy hodnot.

Pravděpodobně to však není to, po čem jste. Chcete-li, aby tato funkce fungovala obecným způsobem, je třeba zkontrolovat typ T a použít typy hodnot odlišné od referenčních typů.

Případně můžete na toto rozhraní umístit omezení rozhraní a rozhraní by mohlo poskytnout způsob, jak zkontrolovat výchozí hodnotu třídy/struct.

2
Reed Copsey

Myslím, že pravděpodobně budete muset tuto logiku rozdělit na dvě části a nejprve zkontrolovat, zda je null.

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

V metodě IsNull se spoléháme na to, že objekty ValueType nemohou být definovány nulovou definicí, takže pokud hodnota je třída, která je odvozena od hodnoty ValueType, už víme, že to není null. Na druhou stranu, pokud to není typ hodnoty, pak můžeme pouze porovnat hodnotu odevzdaného objektu s objektem proti null. Měli bychom se vyhnout kontrole proti ValueType tím, že půjdeme rovnou k obsazení objektu, ale to by znamenalo, že typ hodnoty by se dostal do krabice, což je něco, co bychom se chtěli vyhnout, protože to znamená, že na haldě je vytvořen nový objekt.

V metodě IsNullOrEmpty kontrolujeme speciální případ řetězce. Pro všechny ostatní typy porovnáváme hodnotu (která je již známa ne null) oproti její výchozí hodnotě, která je pro všechny typy referencí null a pro typy hodnot je obvykle nějaká nula (pokud jsou integrální). ).

Pomocí těchto metod se následující kód chová tak, jak byste mohli očekávat:

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) ...
}
1
Damian Powell

Používám:

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