2015-04-24 15 views
7

Data questa interfaccia "IHandle" e due classi per essere gestite:Odd C# comportamento in sede di attuazione generica interfaccia

interface IHandle<T> 
{ 
    void Handle(T m); 
} 


class M1 
{ 
    public int Id; 
} 


class MReset 
{ 
} 

voglio creare una base generica che si occupa di "reset", così come la gestione delle istanze M1 :

class HandlerBase<T> : 
    IHandle<MReset>, 
    IHandle<T> where T : M1 
{ 
    protected int Count; 

    void IHandle<T>.Handle(T m) 
    { 
    ++Count; 
    Console.WriteLine("{0}: Count = {0}", m.Id, Count); 
    } 


    void IHandle<MReset>.Handle(MReset m) 
    { 
    Count = 0; 
    } 
} 

Questo non viene compilata in quanto il compilatore ritiene T possono essere degli "MReset" quindi uscite:

errore CS0 695: 'HandlerBase' non può implementare sia 'IHandle' e 'IHandle' perché possono unire per un certo parametro di tipo sostituzioni

Che di per sé è un po 'strano dato che non riesco a capire come T potrebbe forse essere di tipo MReset poiché deve essere di tipo M1. Ma va bene, posso accettare che il compilatore è più intelligente di me :-)

Edit: Il compilatore non è più intelligente di me :-) Secondo un commento su Why does this result in CS0695? abbiamo "le dichiarazioni di vincolo non sono considerati in sede di determinazione tutti i tipi possibili costruiti ".

Ora scambiare la dichiarazioni di interfaccia:

class HandlerBase<T> : 
    IHandle<T> where T : M1, 
    IHandle<MReset> 
{ 
    ... same as before .. 
} 

E improvvisamente ricevo un messaggio di errore diverso che indica che non posso implementare IHandle.Handle (MReset m) dal momento che la dichiarazione di classe non dice che sta attuando che interfaccia:

errore CS0540: 'HandlerBase.IHandle < ...> Maniglia (MReset)': contiene tipo non implementa l'interfaccia 'IHandle'

Domanda: perché l'ordine delle dichiarazioni fa la differenza? Cosa non va nel secondo esempio?

Alla fine si scopre che c'è una soluzione:

class HandlerBase : 
    IHandle<MReset> 
{ 
    protected int Count; 


    void IHandle<MReset>.Handle(MReset m) 
    { 
    Count = 0; 
    } 
} 


class Handler<T> : HandlerBase, 
    IHandle<T> where T : M1 
{ 
    void IHandle<T>.Handle(T m) 
    { 
    ++Count; 
    Console.WriteLine("{0}: Count = {0}", m.Id, Count); 
    } 
} 

Ma la soluzione funziona solo se HandlerBase implementa IHandle<MReset> - non se l'interfaccia generica IHandle<T> è implementato in HandlerBase prima. Perché?

Edit: Implementazione IHandle<T> in HandlerBasefa lavoro (e se avessi mostrato il codice che qualcuno potrebbe aver visto). Questo funziona:

class HandlerBase<T> : 
    IHandle<T> where T : M1 
{ 
    protected int Count; 

    void IHandle<T>.Handle(T m) 
    { 
    ++Count; 
    Console.WriteLine("Type = {0}, Id = {1}, Count = {2}", GetType(), m.Id, Count); 
    } 
} 


class Handler<T> : HandlerBase<T>, 
    IHandle<MReset> 
    where T : M1 
{ 
    void IHandle<MReset>.Handle(MReset m) 
    { 
    Count = 0; 
    Console.WriteLine("RESET"); 
    } 
} 

Purtroppo la mia seconda dichiarazione di classe è stato questo:

class Handler<T> : HandlerBase<T> where T : M1, 
    IHandle<MReset> 
{ 
    void IHandle<MReset>.Handle(MReset m) 
    { 
    Count = 0; 
    Console.WriteLine("RESET"); 
    } 
} 

Avviso la sottile differenza nella posizione di where T : M1 :-) L'ultimo esempio dichiara che T devono attuare IHandle<MReset> (in aggiunta a M1). Duh!

+0

[correlati/duplicati] (http://stackoverflow.com/questions/15316898/why-does-this-result-in-cs0695). Non sto chiudendo come duplicato solo a causa di questa domanda "perché l'ordine delle dichiarazioni fa la differenza?" a cui non viene data risposta. –

+0

Questo è davvero il link pertinente. Ho appena modificato la risposta accettata per rendere più visibile l'importante informazione dalla specifica C#: ** "Le dichiarazioni di vincoli non vengono prese in considerazione quando si determinano tutti i possibili tipi costruttivi." ** –

risposta

1

Problema risolto: ho trovato la differenza sottile. Quando l'ordine di dichiarazioni viene scambiato dovrei non mossa where T : M1 poiché il vincolo IHandle<MReset> finisce poi applicati a T al posto della dichiarazione di classe:

class HandlerBase<T> : 
    IHandle<T> where T : M1, 
    IHandle<MReset> 
{ 
    ... same as before .. 
} 

la correttezza riordino avrebbe dovuto essere:

class HandlerBase<T> : 
    IHandle<T>, 
    IHandle<MReset> 
    where T : M1 
{ 
    ... same as before .. 
} 
1

@Siram ha sottolineato che il problema unicità (ma non l'aspetto ordine) è stata risolta in Why does this result in CS0695?:

Le specifiche C# lingua (https://www.microsoft.com/en-us/download/confirmation.aspx?id=7029) discute la "unicità di interfacce implementate" in 13.4.2 .: "Le interfacce implementate da una dichiarazione di tipo generico devono rimanere uniche per tutti i possibili tipi costruiti". E più avanti, quando si descrivono i dettagli del controllo: "Le dichiarazioni sui vincoli non sono considerate quando si determinano tutti i tipi possibili costruiti".

Perché così non sono sicuro; forse si possono costruire vincoli annidati o concatenati che rendono impossibile per il compilatore dimostrare l'unicità, oppure non tutti i vincoli possono essere comunicati tramite assemblee (che, penso, sarebbero necessarie per la regola generale della lingua).

+0

Grazie per l'evidenziazione "Le dichiarazioni di vincoli non sono considerato quando si determinano tutti i possibili tipi costruiti ". - questo spiega parti di esso. Sono ancora molto confuso dall'aspetto dell'ordine (come posso vedere sia tu che Siram ne siete consapevoli). –

+0

Apparentemente il compilatore * non considera le dichiarazioni di vincoli in qualche modo inaspettato. Se rimuovo 'where T: M1', l'ordine delle dichiarazioni diventa insignificante e entrambe le sequenze danno come risultato CS0695" ... perché possono unificare per alcune sostituzioni dei parametri di tipo "- come mi aspetterei. –