L'operatore == non può essere applicato a tipi generici in C #?


326

Secondo la documentazione ==dell'operatore in MSDN ,

Per tipi di valore predefiniti, l'operatore di uguaglianza (==) restituisce vero se i valori dei suoi operandi sono uguali, falso altrimenti. Per tipi di riferimento diversi da string, == restituisce true se i suoi due operandi si riferiscono allo stesso oggetto. Per il tipo di stringa, == confronta i valori delle stringhe. I tipi di valori definiti dall'utente possono sovraccaricare l'operatore == (vedi operatore). Anche i tipi di riferimento definiti dall'utente possono essere definiti, sebbene per impostazione predefinita == si comporti come descritto sopra sia per i tipi di riferimento predefiniti che per quelli definiti dall'utente.

Quindi perché questo frammento di codice non viene compilato?

bool Compare<T>(T x, T y) { return x == y; }

Ottengo l'errore L' operatore '==' non può essere applicato agli operandi di tipo 'T' e 'T' . Mi chiedo perché, dal momento che capisco l' ==operatore è predefinito per tutti i tipi?

Modifica: grazie a tutti. All'inizio non ho notato che l'affermazione riguardava solo i tipi di riferimento. Ho anche pensato che il confronto bit per bit è previsto per tutti i tipi di valore, che ora so non è corretto.

Ma, nel caso in cui sto usando un tipo di riferimento, l' ==operatore userebbe il confronto di riferimento predefinito, o userebbe la versione sovraccarica dell'operatore se un tipo ne definisse uno?

Modifica 2: attraverso prove ed errori, abbiamo appreso che l' ==operatore utilizzerà il confronto di riferimento predefinito quando utilizza un tipo generico senza restrizioni. In realtà, il compilatore utilizzerà il metodo migliore che può trovare per l'argomento di tipo limitato, ma non cercherà oltre. Ad esempio, il codice seguente verrà sempre stampato true, anche quando Test.test<B>(new B(), new B())viene chiamato:

class A { public static bool operator==(A x, A y) { return true; } }
class B : A { public static bool operator==(B x, B y) { return false; } }
class Test { void test<T>(T a, T b) where T : A { Console.WriteLine(a == b); } }

Vedi di nuovo la mia risposta per la risposta alla tua domanda di follow-up.
Giovanni Galbo,

Potrebbe essere utile capire che anche senza generici, ci sono alcuni tipi per i quali ==non è consentito tra due operandi dello stesso tipo. Questo vale per i structtipi (tranne i tipi "predefiniti") che non sovraccaricano il file operator ==. A titolo di esempio, prova questo:var map = typeof(string).GetInterfaceMap(typeof(ICloneable)); Console.WriteLine(map == map); /* compile-time error */
Jeppe Stig Nielsen,

Continuando il mio vecchio commento. Ad esempio (vedi altro thread ), con var kvp1 = new KeyValuePair<int, int>(); var kvp2 = kvp1;, quindi non puoi controllare kvp1 == kvp2perché KeyValuePair<,>è uno struct, non è un tipo predefinito C # e non sovraccarica il file operator ==. Tuttavia, viene fornito un esempio var li = new List<int>(); var e1 = li.GetEnumerator(); var e2 = e1;con cui non è possibile eseguire e1 == e2(qui abbiamo la struttura nidificata List<>.Enumerator(chiamata "List`1+Enumerator[T]"dal runtime) che non sovraccarica ==).
Jeppe Stig Nielsen,

RE: "Allora perché questo frammento di codice non viene compilato?" - Ehm ... perché non puoi restituire un boolda un void...
BrainSlugs83

1
@ BrainSlugs83 Grazie per aver catturato un bug di 10 anni!
Hosam Aly,

Risposte:


143

"... per impostazione predefinita == si comporta come descritto sopra per i tipi di riferimento predefiniti e definiti dall'utente."

Il tipo T non è necessariamente un tipo di riferimento, quindi il compilatore non può fare questa ipotesi.

Tuttavia, questo verrà compilato perché è più esplicito:

    bool Compare<T>(T x, T y) where T : class
    {
        return x == y;
    }

Seguire la domanda aggiuntiva "Ma, nel caso in cui sto usando un tipo di riferimento, l'operatore == userebbe il confronto di riferimento predefinito o userebbe la versione sovraccarica dell'operatore se un tipo ne definisse uno?"

Avrei pensato che == su Generics avrebbe usato la versione sovraccarica, ma il seguente test dimostra il contrario. Interessante ... Mi piacerebbe sapere perché! Se qualcuno lo sa, per favore condividi.

namespace TestProject
{
 class Program
 {
    static void Main(string[] args)
    {
        Test a = new Test();
        Test b = new Test();

        Console.WriteLine("Inline:");
        bool x = a == b;
        Console.WriteLine("Generic:");
        Compare<Test>(a, b);

    }


    static bool Compare<T>(T x, T y) where T : class
    {
        return x == y;
    }
 }

 class Test
 {
    public static bool operator ==(Test a, Test b)
    {
        Console.WriteLine("Overloaded == called");
        return a.Equals(b);
    }

    public static bool operator !=(Test a, Test b)
    {
        Console.WriteLine("Overloaded != called");
        return a.Equals(b);
    }
  }
}

Produzione

In linea: sovraccarico == chiamato

Generico:

Premere un tasto qualsiasi per continuare . . .

Follow-up 2

Voglio sottolineare che cambiando il mio metodo di confronto in

    static bool Compare<T>(T x, T y) where T : Test
    {
        return x == y;
    }

causa la chiamata dell'operatore overload ==. Immagino che senza specificare il tipo (come dove ), il compilatore non può dedurre che dovrebbe usare l'operatore sovraccarico ... anche se penso che avrebbe abbastanza informazioni per prendere quella decisione anche senza specificare il tipo.


Grazie. Non ho notato che l'affermazione riguardava solo i tipi di riferimento.
Hosam Aly,

4
Ri: Follow Up 2: In realtà il compilatore lo collegherà al metodo migliore che trova, che è in questo caso Test.op_Equal. Ma se hai una classe che deriva da Test e sovrascrive l'operatore, l'operatore di Test verrà comunque chiamato.
Hosam Aly,

4
La buona pratica che vorrei sottolineare è che si dovrebbe sempre fare il confronto effettivo all'interno di un Equalsmetodo ignorato (non nell'operatore ==).
jpbochi,

11
La risoluzione del sovraccarico avviene in fase di compilazione. Quindi, quando abbiamo ==tra tipi generici Te T, si trova il sovraccarico migliore, dati i vincoli che sono portati T(c'è una regola speciale che non impacchetterà mai un tipo di valore per questo (che darebbe un risultato insignificante), quindi ci deve essere qualche vincolo che garantisce che sia un tipo di riferimento). Nel tuo follow-up 2 , se entri con DerivedTestoggetti e DerivedTestderiva da Testma introduce un nuovo sovraccarico di ==, avrai di nuovo il "problema". Quale sovraccarico viene chiamato, viene "bruciato" nell'IL al momento della compilazione.
Jeppe Stig Nielsen il

1
stranamente, questo sembra funzionare per tipi di riferimento generali (dove ci si aspetterebbe che questo confronto sia sull'uguaglianza di riferimento) ma per le stringhe sembra usare anche l'uguaglianza di riferimento - così puoi finire per confrontare 2 stringhe identiche e avere == (quando in un metodo generico con vincolo di classe) afferma che sono diversi.
JonnyRaa,

292

Come altri hanno già detto, funzionerà solo quando T è vincolato ad essere un tipo di riferimento. Senza alcun vincolo, puoi confrontare con null, ma solo null - e tale confronto sarà sempre falso per i tipi di valore non annullabili.

Invece di chiamare Equals, è meglio usare un IComparer<T>- e se non hai più informazioni, EqualityComparer<T>.Defaultè una buona scelta:

public bool Compare<T>(T x, T y)
{
    return EqualityComparer<T>.Default.Equals(x, y);
}

A parte qualsiasi altra cosa, questo evita boxe / casting.


Grazie. Stavo cercando di scrivere una semplice classe wrapper, quindi volevo solo delegare l'operazione al membro wrapping effettivo. Ma conoscendo EqualityComparer <T>. Il default sicuramente mi ha aggiunto valore. :)
Hosam Aly,

Minore a parte, Jon; potresti voler notare il commento re pobox vs yoda sul mio post.
Marc Gravell

4
Bel consiglio sull'uso di EqualityComparer <T>
chakrit,

1
+1 per indicare che può essere paragonato a null e per tipo di valore non annullabile sarà sempre falso
Jalal Said

@BlueRaja: Sì, perché esistono regole speciali per i confronti con il valore letterale null. Quindi "senza alcun vincolo, puoi confrontare con null, ma solo null". È già nella risposta. Quindi, perché esattamente questo non può essere corretto?
Jon Skeet,

41

In generale, EqualityComparer<T>.Default.Equalsdovrebbe fare il lavoro con tutto ciò che implementa IEquatable<T>o che ha Equalsun'implementazione ragionevole .

Se, tuttavia, ==e Equalssono implementati diversamente per qualche motivo, il mio lavoro sugli operatori generici dovrebbe essere utile; supporta le versioni operatore di (tra gli altri):

  • Uguale (valore T1, valore T2)
  • NotEqual (valore T1, valore T2)
  • Maggiore (valore T1, valore T2)
  • Meno di (valore T1, valore T2)
  • GreaterThanOrEqual (valore T1, valore T2)
  • LessThanOrEqual (valore T1, valore T2)

Biblioteca molto interessante! :) (Nota a margine: posso suggerire di utilizzare il collegamento a www.yoda.arachsys.com, perché quello del pobox è stato bloccato dal firewall nel mio posto di lavoro? È possibile che altri possano affrontare lo stesso problema.)
Hosam Aly

L'idea è che pobox.com/~skeet indicherà sempre il mio sito Web, anche se si sposta altrove. Io tendo a postare link tramite pobox.com per il bene dei posteri - ma è possibile attualmente sostituire yoda.arachsys.com invece.
Jon Skeet,

Il problema con pobox.com è che si tratta di un servizio di posta elettronica basato sul Web (o almeno così dice il firewall dell'azienda), quindi è bloccato. Ecco perché non ho potuto seguire il suo link.
Hosam Aly,

"Se, tuttavia, == e uguali sono implementati in modo diverso per qualche motivo" - Holy smoke! Che comunque! Forse ho solo bisogno di vedere un caso d'uso al contrario, ma una libreria con semantica divergente uguale probabilmente incontrerà problemi più grandi che problemi con i generici.
Edward Brey,

@EdwardBrey non ti sbagli; sarebbe bello se il compilatore potesse
farcela

31

Così tante risposte, e non una sola spiega il PERCHÉ? (che Giovanni ha esplicitamente chiesto) ...

I generici .NET non si comportano come modelli C ++. Nei modelli C ++, la risoluzione del sovraccarico si verifica dopo che sono noti i parametri del modello effettivo.

Nei generici .NET (incluso C #), la risoluzione del sovraccarico si verifica senza conoscere i parametri generici effettivi. Le uniche informazioni che il compilatore può utilizzare per scegliere la funzione da chiamare provengono dai vincoli di tipo sui parametri generici.


2
ma perché il compilatore non può trattarli come un oggetto generico? dopo tutto ==funziona per tutti i tipi che si tratti di tipi di riferimento o tipi di valore. Questa dovrebbe essere la domanda a cui non credo tu abbia risposto.
nawfal,

4
@nawfal: in realtà no, ==non funziona per tutti i tipi di valore. Ancora più importante, non ha lo stesso significato per tutti i tipi, quindi il compilatore non sa cosa farne.
Ben Voigt,

1
Ben, oh sì, ho perso le strutture personalizzate che possiamo creare senza ==. Puoi includere anche quella parte nella tua risposta, come immagino sia questo il punto principale qui
nawfal

12

La compilazione non può sapere che T non può essere una struttura (tipo di valore). Quindi devi dire che può essere solo di tipo di riferimento, penso:

bool Compare<T>(T x, T y) where T : class { return x == y; }

È perché se T potrebbe essere un tipo di valore, potrebbero esserci casi in cui si x == yformerebbe male - nei casi in cui un tipo non ha un operatore == definito. Lo stesso accadrà per questo che è più ovvio:

void CallFoo<T>(T x) { x.foo(); }

Anche questo fallisce, perché potresti passare un tipo T che non avrebbe una funzione foo. C # ti obbliga a assicurarti che tutti i tipi possibili abbiano sempre una funzione foo. Questo è fatto dalla clausola where.


1
Grazie per il chiarimento. Non sapevo che i tipi di valore non supportassero l'operatore == out of the box.
Hosam Aly,

1
Hosam, ho testato con gmcs (mono) e confronta sempre i riferimenti. (ovvero non utilizza un operatore definito facoltativamente == per T)
Johannes Schaub - litb

C'è un avvertimento con questa soluzione: l'operatore == non può essere sovraccaricato; vedi questa domanda StackOverflow .
Dimitri C.

8

Sembra che senza il vincolo di classe:

bool Compare<T> (T x, T y) where T: class
{
    return x == y;
}

Bisogna rendersi conto che mentre l' operatore eredita un classvincolo , mentre quello di una struttura ha la precedenzaEquals==Object.EqualsValueType.Equals .

Nota che:

bool Compare<T> (T x, T y) where T: struct
{
    return x == y;
}

dà anche lo stesso errore del compilatore.

Finora non capisco perché il compilatore abbia rifiutato un confronto operatore di uguaglianza di tipo valore. So per certo che questo funziona:

bool Compare<T> (T x, T y)
{
    return x.Equals(y);
}

sai che sono un noob c # totale. ma penso che fallisca perché il compilatore non sa cosa fare. poiché T non è ancora noto, ciò che viene fatto dipende dal tipo T se i tipi di valore sarebbero consentiti. per i riferimenti, i riferimenti vengono semplicemente confrontati indipendentemente da T. se lo fai .Equals, quindi .Equal viene appena chiamato.
Johannes Schaub - litb

ma se lo fai == su un tipo di valore, il tipo di valore non deve implementare necassary quell'operatore.
Johannes Schaub - litb

Ciò avrebbe senso, chiaro :) È possibile che le strutture definite dall'utente non sovraccarichino ==, quindi il compilatore fallisce.
Jon Limjap,

2
Il primo metodo di confronto non utilizza Object.Equalsma verifica invece l'uguaglianza di riferimento. Ad esempio, Compare("0", 0.ToString())restituirebbe false, poiché gli argomenti sarebbero riferimenti a stringhe distinte, entrambe con uno zero come unico carattere.
supercat,

1
Minore gotcha su quest'ultimo - non lo hai limitato a strutture, quindi NullReferenceExceptionpotrebbe succedere.
Flynn1179,

6

Beh, nel mio caso volevo testare l'operatore di uguaglianza. Avevo bisogno di chiamare il codice sotto gli operatori di uguaglianza senza impostare esplicitamente il tipo generico. I consigli per EqualityComparernon sono stati utili come metodo EqualityComparerchiamato Equalsma non l'operatore di uguaglianza.

Ecco come ho ottenuto questo lavoro con tipi generici creando un LINQ. Chiama il codice giusto per ==e !=operatori:

/// <summary>
/// Gets the result of "a == b"
/// </summary>
public bool GetEqualityOperatorResult<T>(T a, T b)
{
    // declare the parameters
    var paramA = Expression.Parameter(typeof(T), nameof(a));
    var paramB = Expression.Parameter(typeof(T), nameof(b));
    // get equality expression for the parameters
    var body = Expression.Equal(paramA, paramB);
    // compile it
    var invokeEqualityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
    // call it
    return invokeEqualityOperator(a, b);
}

/// <summary>
/// Gets the result of "a =! b"
/// </summary>
public bool GetInequalityOperatorResult<T>(T a, T b)
{
    // declare the parameters
    var paramA = Expression.Parameter(typeof(T), nameof(a));
    var paramB = Expression.Parameter(typeof(T), nameof(b));
    // get equality expression for the parameters
    var body = Expression.NotEqual(paramA, paramB);
    // compile it
    var invokeInequalityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
    // call it
    return invokeInequalityOperator(a, b);
}

4

C'è una voce MSDN Connect per questo qui

La risposta di Alex Turner inizia con:

Sfortunatamente, questo comportamento è in base alla progettazione e non esiste una soluzione semplice per consentire l'uso di == con parametri di tipo che possono contenere tipi di valore.


4

Se vuoi assicurarti che vengano chiamati gli operatori del tuo tipo personalizzato, puoi farlo tramite la riflessione. Basta ottenere il tipo utilizzando il parametro generico e recuperare MethodInfo per l'operatore desiderato (ad esempio op_Equality, op_Inequality, op_LessThan ...).

var methodInfo = typeof (T).GetMethod("op_Equality", 
                             BindingFlags.Static | BindingFlags.Public);    

Quindi eseguire l'operatore utilizzando il metodo Invoke di MethodInfo e passare gli oggetti come parametri.

var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2});

Ciò invocherà l'operatore sovraccarico e non quello definito dai vincoli applicati al parametro generico. Potrebbe non essere pratico, ma potrebbe tornare utile per testare le unità degli operatori quando si utilizza una classe base generica che contiene un paio di test.


3

Ho scritto la seguente funzione guardando l'ultimo msdn. Può facilmente confrontare due oggetti xe y:

static bool IsLessThan(T x, T y) 
{
    return ((IComparable)(x)).CompareTo(y) <= 0;
}

4
Puoi sbarazzarti dei tuoi booleani e scriverereturn ((IComparable)(x)).CompareTo(y) <= 0;
aloisdg passando a codidact.com il

1

bool Compare(T x, T y) where T : class { return x == y; }

Quanto sopra funzionerà perché == è curato in caso di tipi di riferimento definiti dall'utente.
In caso di tipi di valore, == può essere ignorato. In tal caso, dovrebbe essere definito anche "! =".

Penso che potrebbe essere la ragione, non consente il confronto generico utilizzando "==".


2
Grazie. Credo che anche i tipi di riferimento possano sovrascrivere l'operatore. Ma il motivo del fallimento è ora chiaro.
Hosam Aly,

1
Il ==token viene utilizzato per due diversi operatori. Se per i tipi di operando indicati esiste un sovraccarico compatibile dell'operatore di uguaglianza, tale sovraccarico verrà utilizzato. Altrimenti se entrambi gli operandi sono tipi di riferimento compatibili tra loro, verrà utilizzato un confronto di riferimento. Si noti che nel Comparemetodo sopra il compilatore non può dire che si applica il primo significato, ma può dire che si applica il secondo significato, quindi il ==token utilizzerà quest'ultimo anche se Tsovraccarica l'operatore di controllo dell'uguaglianza (ad esempio se è di tipo String) .
supercat,

0

Il .Equals()funziona per me, mentre TKeyè un tipo generico.

public virtual TOutputDto GetOne(TKey id)
{
    var entity =
        _unitOfWork.BaseRepository
            .FindByCondition(x => 
                !x.IsDelete && 
                x.Id.Equals(id))
            .SingleOrDefault();


    // ...
}

Non è x.Id.Equalscosì id.Equals. Presumibilmente, il compilatore sa qualcosa sul tipo di x.
Hosam Aly,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.