Pergunta

Eu tenho um método genérico definido assim:

public void MyMethod<T>(T myArgument)

A primeira coisa que eu quero fazer é verificar se o valor de myArgument é o valor padrão para esse tipo, algo como isto:

if (myArgument == default(T))

Mas isso não compila porque eu ainda não é garantido que irá implementar o operador==.Então eu mudei o código para isso:

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

Agora isso compila, mas não conseguirão se myArgument é nula, que é parte do que estou testando.Eu posso adicionar um null explícito verificar como esta:

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

Agora, este sente-se redundante para mim.ReSharper é mesmo sugerindo que eu altere o myArgument == null parte em myArgument == default(T), que é onde eu comecei.Existe uma forma melhor de resolver esse problema?

Eu preciso de apoio ambos referências e tipos de tipos de valor.

Foi útil?

Solução

Para evitar o boxe, a melhor forma de comparar os genéricos para a igualdade é com EqualityComparer<T>.Default.Este respeita IEquatable<T> (sem boxe), bem como object.Equals, e trata de todas as Nullable<T> "levantou" nuances.Por isso:

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

Isso irá corresponder a:

  • nulo para as classes
  • nulo (vazio) para Nullable<T>
  • zero/false/etc para outras structs

Outras dicas

Como sobre isso:

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

Usando o static object.Equals() método evita a necessidade de fazer o null confira você mesmo.Explicitamente qualificação a chamada com object. provavelmente não será necessário, dependendo do seu contexto, mas eu normalmente prefixo static chamadas com o nome do tipo apenas para tornar o código mais solúvel.

Fui capaz de localizar um Microsoft Connect artigo que aborda esse problema em alguns detalhes:

Infelizmente, esse comportamento é por design e não há uma solução fácil para habilitar o uso de com o tipo de parâmetros que podem conter tipos de valor.

Se os tipos são conhecidos como tipos de referência, o padrão de sobrecarga do definido no objecto de testes de variáveis para a igualdade de referência, embora de um tipo pode especificar o seu próprio personalizado de sobrecarga.O compilador determina que a sobrecarga de a utilizar com base no tipo estático da variável (a determinação não é polimórfico).Portanto, se você alterar o seu exemplo, para restringir o parâmetro de tipo genérico T para um não-seladas tipo de referência (como Exceção), o compilador pode determinar a sobrecarga específica para o uso e o seguinte código compila:

public class Test<T> where T : Exception

Se os tipos são conhecidos como tipos de valor, executa valor específico testes de igualdade com base na exata tipos usados.Não há nenhuma boa "padrão" comparação aqui desde referência comparações não são significativas no valor de tipos e o compilador não é possível saber qual o valor de comparação a emitir.O compilador pode emitir uma chamada para ValueType.É igual a(Objeto), mas este método usa a reflexão e é bastante ineficazes em relação ao valor específico comparações.Portanto, mesmo se você especificar um valor de tipo de restrição em T, não é nada razoável para o compilador para gerar aqui:

public class Test<T> where T : struct

No caso apresentado, onde o compilador não sei mesmo se T é um valor ou tipo de referência, não é da mesma forma nada para gerar o que seria válido para todos os tipos possíveis.Uma referência de comparação não seria válido para tipos de valor e algum tipo de comparação de valor inesperado para tipos de referência que não a sobrecarga.

Aqui está o que você pode fazer...

Eu tenho validado que ambos os métodos de trabalho para um genérico comparação de referência e tipos de valor:

object.Equals(param, default(T))

ou

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

Para fazer comparações com o operador "==" você vai precisar usar um dos seguintes métodos:

Se todos os casos de T derivar a partir de uma classe base, você pode deixar que o compilador sabe usar genérico tipo de restrições.

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

O compilador reconhece, em seguida, como para executar operações em MyBase e não vai jogar o "Operador '==' não pode ser aplicada para operandos do tipo " T " e " T "erro" que você está vendo agora.

Outra opção seria a de restringir o T a qualquer tipo que implementa IComparable.

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

E, em seguida, usar o CompareTo método definido pela Interface IComparable.

Tente isso:

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

que deve compilar e fazer o que você quer.

(Editado)

Marc Gravell tem a melhor resposta, mas eu queria postar um simples trecho de código que eu trabalhei até o demonstrar.Basta executar este C# aplicativo de console:

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

Mais uma coisa:pode alguém com o VS2008 tente isso como um método de extensão?Eu estou preso com 2005 aqui e estou curioso para ver se isso seria permitido.


Editar: Aqui está como fazê-lo funcionar como um método de extensão:

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

Para lidar com todos os tipos de T, incluindo onde T é um tipo primitivo, você precisará compilar em ambos os métodos de comparação:

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

Não vai ser um problema aqui -

Se você estiver indo para permitir que isso funcione para qualquer tipo de padrão(T) sempre será null para tipos de referência, e 0 (ou estrutura completa de 0) para tipos de valor.

Provavelmente, este não é o comportamento que você está depois, no entanto.Se você quer que isso funcione de uma forma genérica, provavelmente será necessário usar a reflexão para verificar o tipo de T, e o valor do identificador de tipos diferentes de tipos de referência.

Como alternativa, você poderia colocar uma restrição de interface no presente, e a interface poderia fornecer uma maneira para verificar se contra o padrão da classe/estrutura.

Eu acho que você provavelmente terá que dividir essa lógica em duas partes e verificar nulo.

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

No IsNull método, estamos contando com o fato de que ValueType objetos não podem ser nulos, por definição, então, se o valor passa a ser uma classe que deriva de ValueType, nós já sabemos que ele não é nulo.Por outro lado, se não é um tipo de valor, em seguida, podemos apenas comparar o valor convertido em um objeto contra nulo.Poderíamos evitar a seleção contra ValueType indo direto para um elenco de objeto, mas isso significaria que um tipo de valor ficaria em caixa, que é algo que provavelmente quer evitar, pois implica que um novo objeto é criado no heap.

Em IsNullOrEmpty método, estamos verificando para o caso especial de uma seqüência de caracteres.Para todos os outros tipos, estamos comparando o valor (o que já sabemos é não null), contra um valor padrão que para todos os tipos de referência é nula e para tipos de valor normalmente é alguma forma de zero (se eles estiverem integral).

Utilizando estes métodos, o código a seguir funciona como você poderia esperar:

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) ...
}

Eu uso:

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

Não sei se isso funciona com os seus requisitos, ou não, mas você pode restringir o T é um Tipo que implementa uma interface, tais como IComparable e, em seguida, usar o ComparesTo (): método da interface (que IIRC suporta/alças nulos) como este:

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

Provavelmente existem outras interfaces que você pode usar como bem IEquitable, etc.

@ilitirit:

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

Operador '==' não pode ser aplicada para operandos do tipo " T " e 'T'

Eu não posso pensar em uma maneira de fazer isso sem a explícita null teste, seguido de invocar o método de Equals ou objeto.É igual a, como sugerido acima.

Você pode planejar uma solução usando o Sistema.A comparação, mas realmente, isso vai acabar com a maneira mais linhas de código e aumentar a complexidade substancialmente.

Eu acho que você estava perto.

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

Agora isso compila, mas não conseguirão se myArgument é nula, que é parte do que estou testando.Eu posso adicionar um null explícito verificar como esta:

Você só precisa inverter o objeto sobre o qual o igual está sendo chamado para um elegante nulo abordagem segura.

default(T).Equals(myArgument);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top