La classe anonima può implementare l'interfaccia?


463

È possibile avere un tipo anonimo implementare un'interfaccia?

Ho un pezzo di codice che mi piacerebbe lavorare, ma non so come farlo.

Ho avuto un paio di risposte che dicono di no o creano una classe che implementa l'interfaccia costruendone nuove istanze. Questo non è proprio l'ideale, ma mi chiedo se esiste un meccanismo per creare una classe dinamica sottile sopra un'interfaccia che lo renderebbe semplice.

public interface DummyInterface
{
    string A { get; }
    string B { get; }
}

public class DummySource
{
    public string A { get; set; }
    public string C { get; set; }
    public string D { get; set; }
}

public class Test
{
    public void WillThisWork()
    {
        var source = new DummySource[0];
        var values = from value in source
                     select new
                     {
                         A = value.A,
                         B = value.C + "_" + value.D
                     };

        DoSomethingWithDummyInterface(values);

    }

    public void DoSomethingWithDummyInterface(IEnumerable<DummyInterface> values)
    {
        foreach (var value in values)
        {
            Console.WriteLine("A = '{0}', B = '{1}'", value.A, value.B);
        }
    }
}

Ho trovato un articolo Wrap di interfaccia dinamica che descrive un approccio. È questo il modo migliore per farlo?


1
Il link appare obsoleto, forse una valida alternativa a liensberger.it/web/blog/?p=298 .
Phil Cooper,

1
Sì, puoi farlo con .NET 4 e versioni successive (tramite DLR), usando il pacchetto nuget ImpromptuInterface .
BrainSlugs83

1
@PhilCooper Il tuo link è inattivo, probabilmente da almeno il 2016, ma per fortuna è stato archiviato prima di allora. web.archive.org/web/20111105150920/http://www.liensberger.it/…
Paul

Risposte:


361

No, i tipi anonimi non possono implementare un'interfaccia. Dalla guida alla programmazione C # :

I tipi anonimi sono tipi di classe costituiti da una o più proprietà pubbliche di sola lettura. Non sono ammessi altri tipi di membri della classe come metodi o eventi. Un tipo anonimo non può essere trasmesso a nessuna interfaccia o tipo ad eccezione dell'oggetto.


5
sarebbe bello avere questa roba comunque. Se stai parlando di leggibilità del codice, le espressioni lambda di solito non sono la strada da percorrere. Se stiamo parlando di RAD, mi piacciono tutti l'implementazione di un'interfaccia anonima simile a Java. A proposito, in alcuni casi questa caratteristica è più potente dei delegati
Arsen Zahray,

18
@ArsenZahray: le espressioni lambda, se usate bene, aumentano effettivamente la leggibilità del codice. Sono particolarmente potenti se utilizzati in catene funzionali, che possono ridurre o eliminare la necessità di variabili locali.
Roy Tinker,

2
Potresti fare il trucco in questo modo "Classi di implementazione anonime - Un modello di progettazione per C #" - twistedoakstudios.com/blog/…
Dmitry Pavlov

3
@DmitryPavlov, che è stato sorprendentemente prezioso. Passanti: ecco la versione ridotta.
kdbanman,

1
si può lanciare il tipo anonimo ad un oggetto anonimo con gli stessi campi stackoverflow.com/questions/1409734/cast-to-anonymous-type
Zinov

90

Mentre questa potrebbe essere una domanda di due anni e mentre le risposte nel thread sono tutte abbastanza vere, non posso resistere all'impulso di dirti che in effetti è possibile avere una classe anonima implementare un'interfaccia, anche se ci vuole un un po 'di imbroglio creativo per arrivarci.

Nel 2008 stavo scrivendo un provider LINQ personalizzato per il mio allora datore di lavoro, e ad un certo punto dovevo essere in grado di distinguere le "mie" classi anonime da altre anonime, il che significava che avevano implementato un'interfaccia che avrei potuto usare per digitare check loro. Il modo in cui l'abbiamo risolto è stato utilizzando gli aspetti (abbiamo usato PostSharp ), per aggiungere l'implementazione dell'interfaccia direttamente nell'IL . Quindi, infatti, lasciare che le classi anonime implementino le interfacce è fattibile , devi solo piegare leggermente le regole per arrivarci.


8
@Gusdor, in questo caso, avevamo il controllo completo sulla build ed era sempre eseguito su una macchina dedicata. Inoltre, dal momento che stavamo usando PostSharp, e quello che stavamo facendo è completamente legale all'interno di quel framework, nulla potrebbe davvero essere pop finché ci assicuriamo che PostSharp sia installato sul server di build che stiamo usando.
Mia Clarke,

16
@Gusdor Sono d'accordo che dovrebbe essere facile per gli altri programmatori ottenere il progetto e compilarlo senza grandi difficoltà, ma questo è un problema diverso che può essere risolto separatamente, senza evitare completamente strumenti o framework come PostSharp. Lo stesso argomento che potresti fare potrebbe essere fatto contro VS stesso o qualsiasi altro framework MS non standard che non fa parte delle specifiche C #. Hai bisogno di queste cose, altrimenti "diventa pop". Questo non è un problema IMO. Il problema è quando la build diventa così complicata che è difficile far funzionare bene tutte queste cose.
AaronLS

6
@ZainRizvi No, non è così. Per quanto ne so, è ancora in produzione. La preoccupazione sollevata mi sembrava strana allora, e al massimo arbitraria per me adesso. Sostanzialmente stava dicendo "Non usare un framework, le cose si romperanno!". Non l'hanno fatto, nulla è diventato pop e non sono sorpreso.
Mia Clarke,

3
Ora è molto più facile; non è necessario modificare l'IL dopo la generazione del codice; basta usare ImpromptuInterface . - Ti consente di associare qualsiasi oggetto (compresi gli oggetti digitati in modo anonimo) a qualsiasi interfaccia (ovviamente ci saranno eccezioni di associazione tardiva se provi a utilizzare una parte dell'interfaccia che la classe non supporta effettivamente).
BrainSlugs83

44

Lanciare tipi anonimi su interfacce è stato qualcosa che volevo da un po ', ma sfortunatamente l'attuale implementazione ti costringe ad avere un'implementazione di quell'interfaccia.

La migliore soluzione è avere un tipo di proxy dinamico che crea l'implementazione per te. Utilizzando l'eccellente progetto LinFu è possibile sostituire

select new
{
  A = value.A,
  B = value.C + "_" + value.D
};

con

 select new DynamicObject(new
 {
   A = value.A,
   B = value.C + "_" + value.D
 }).CreateDuck<DummyInterface>();

17
Il progetto Interfaccia improvvisata lo farà in .NET 4.0 usando il DLR ed è più leggero di Linfu.
jbtule

È DynamicObjectun tipo LinFu? System.Dynamic.DynamicObjectha solo un costruttore protetto (almeno in .NET 4.5).
jdmcnair,

Sì. Mi riferivo all'implementazione di LinFu DynamicObjectche precede la versione DLR
Arne Claassen,

15

I tipi anonimi possono implementare interfacce tramite un proxy dinamico.

Ho scritto un metodo di estensione su GitHub e un post sul blog http://wblo.gs/feE per supportare questo scenario.

Il metodo può essere utilizzato in questo modo:

class Program
{
    static void Main(string[] args)
    {
        var developer = new { Name = "Jason Bowers" };

        PrintDeveloperName(developer.DuckCast<IDeveloper>());

        Console.ReadKey();
    }

    private static void PrintDeveloperName(IDeveloper developer)
    {
        Console.WriteLine(developer.Name);
    }
}

public interface IDeveloper
{
    string Name { get; }
}

13

No; un tipo anonimo non può essere fatto per fare nulla se non avere alcune proprietà. Dovrai creare il tuo tipo. Non ho letto in dettaglio l'articolo collegato, ma sembra che usi Reflection.Emit per creare nuovi tipi al volo; ma se limiti la discussione alle cose all'interno di C # stesso non puoi fare quello che vuoi.


E importante notare: le proprietà possono includere anche funzioni o vuoti (Azione): selezionare new {... MyFunction = new Func <string, bool> (s => value.A == s)} funziona sebbene non sia possibile fare riferimento a nuove proprietà nelle tue funzioni (non possiamo usare "A" al posto di "value.A").
cfeduke,

2
Bene, non è solo una proprietà che sembra essere un delegato? In realtà non è un metodo.
Marc Gravell

1
Ho usato Reflection.Emit per creare tipi in fase di runtime, ma ora credo che preferirei una soluzione AOP per evitare i costi di runtime.
Norman H,

11

La soluzione migliore è semplicemente non usare classi anonime.

public class Test
{
    class DummyInterfaceImplementor : IDummyInterface
    {
        public string A { get; set; }
        public string B { get; set; }
    }

    public void WillThisWork()
    {
        var source = new DummySource[0];
        var values = from value in source
                     select new DummyInterfaceImplementor()
                     {
                         A = value.A,
                         B = value.C + "_" + value.D
                     };

        DoSomethingWithDummyInterface(values.Cast<IDummyInterface>());

    }

    public void DoSomethingWithDummyInterface(IEnumerable<IDummyInterface> values)
    {
        foreach (var value in values)
        {
            Console.WriteLine("A = '{0}', B = '{1}'", value.A, value.B);
        }
    }
}

Si noti che è necessario eseguire il cast del risultato della query sul tipo di interfaccia. Potrebbe esserci un modo migliore per farlo, ma non sono riuscito a trovarlo.


2
Puoi usare al values.OfType<IDummyInterface>()posto del cast. Restituisce solo gli oggetti nella tua raccolta che possono essere effettivamente lanciati su quel tipo. Tutto dipende da quello che vuoi.
Kristoffer L

7

La risposta alla domanda specificamente posta è no. Ma hai preso in considerazione quadri beffardi? Uso MOQ ma ce ne sono milioni là fuori e ti permettono di implementare / stub (parzialmente o completamente) interfacce in linea. Per esempio.

public void ThisWillWork()
{
    var source = new DummySource[0];
    var mock = new Mock<DummyInterface>();

    mock.SetupProperty(m => m.A, source.Select(s => s.A));
    mock.SetupProperty(m => m.B, source.Select(s => s.C + "_" + s.D));

    DoSomethingWithDummyInterface(mock.Object);
}

1

Un'altra opzione è quella di creare un'unica classe di implementazione concreta che accetta lambda nel costruttore.

public interface DummyInterface
{
    string A { get; }
    string B { get; }
}

// "Generic" implementing class
public class Dummy : DummyInterface
{
    private readonly Func<string> _getA;
    private readonly Func<string> _getB;

    public Dummy(Func<string> getA, Func<string> getB)
    {
        _getA = getA;
        _getB = getB;
    }

    public string A => _getA();

    public string B => _getB();
}

public class DummySource
{
    public string A { get; set; }
    public string C { get; set; }
    public string D { get; set; }
}

public class Test
{
    public void WillThisWork()
    {
        var source = new DummySource[0];
        var values = from value in source
                     select new Dummy // Syntax changes slightly
                     (
                         getA: () => value.A,
                         getB: () => value.C + "_" + value.D
                     );

        DoSomethingWithDummyInterface(values);

    }

    public void DoSomethingWithDummyInterface(IEnumerable<DummyInterface> values)
    {
        foreach (var value in values)
        {
            Console.WriteLine("A = '{0}', B = '{1}'", value.A, value.B);
        }
    }
}

Se tutto ciò che hai intenzione di fare è convertire DummySourcein DummyInterface, allora sarebbe più semplice avere una sola classe che prende un DummySourcenel costruttore e implementa l'interfaccia.

Ma, se è necessario convertire molti tipi in DummyInterface, questo è molto meno piastra della caldaia.

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.