2011-01-22 20 views
22

Sono in procinto di scrivere un'implementazione C# Wicket al fine di approfondire la mia comprensione di C# e Wicket. Uno dei problemi che stiamo incontrando è che Wicket fa un uso pesante delle classi interne anonime e C# non ha classi interne anonime.Classi interne anonime in C#

Così, per esempio, in Wicket, si definisce un link come questo:

Link link = new Link("id") { 
    @Override 
    void onClick() { 
     setResponsePage(...); 
    } 
}; 

Dal Link è una classe astratta, costringe l'implementor di implementare un metodo onClick.

Tuttavia, in C#, poiché non ci sono classi interne anonime, non c'è modo di farlo. In alternativa, è possibile utilizzare eventi come questo:

var link = new Link("id"); 
link.Click += (sender, eventArgs) => setResponsePage(...); 

Naturalmente, ci sono un paio di inconvenienti con questo. Prima di tutto, ci possono essere più gestori di Click, che potrebbero non essere interessanti. Inoltre non forza l'implementatore ad aggiungere un gestore Click.

Un'altra opzione potrebbe essere quella di avere solo una proprietà di chiusura come questa:

var link = new Link("id"); 
link.Click =() => setResponsePage(...); 

Questo risolve il problema di avere molti gestori, ma ancora non forzare l'implementor per aggiungere il gestore.

Quindi, la mia domanda è, come si emula qualcosa di simile in C# idiomatico?

+1

Non vedo una classe interiore anonima nell'esempio che hai dato. Se si desidera che gli implementatori della classe astratta implementino sempre alcuni metodi, è possibile creare un metodo astratto nella classe o implementare un'interfaccia. – tenor

+2

@tenor, esiste una classe anonima inline definita che eredita da 'Link' e sovrascrive il metodo' onClick'. A differenza di Java, C# non supporta le classi anonime per derivare da un determinato tipo di utente. –

+0

@Darin Dimitrov, grazie per averlo indicato. Stavo cercando una vera classe "interiore/nidificata". L'esempio fornito assomiglia più ad una classe anonima che deriva da una classe esistente, almeno nel linguaggio C#. – tenor

risposta

17

È possibile fare in modo che il delegato faccia parte del costruttore della classe Link. In questo modo l'utente dovrà aggiungerlo.

public class Link 
{ 
    public Link(string id, Action handleStuff) 
    { 
     ... 
    } 

} 

Quindi si crea un'istanza in questo modo:

var link = new Link("id",() => do stuff); 
+0

Sì, funzionerebbe bene anche con i parametri denominati. – cdmckay

1

ho iniziato questa prima buona risposta @ di meatthew - avrei fatto quasi esattamente lo stesso, tranne - tranne che Vorrei iniziare con una classe base astratta - così, se non volessi intraprendere il percorso di un'implementazione anonima, saresti libero di farlo anche tu.

public abstract class LinkBase 
{ 
    public abstract string Name { get; } 
    protected abstract void OnClick(object sender, EventArgs eventArgs); 
    //... 
} 

public class Link : LinkBase 
{ 
    public Link(string name, Action<object, EventArgs> onClick) 
    { 
     _name = Name; 
     _onClick = onClick; 
    } 

    public override string Name 
    { 
     get { return _name; } 
    } 

    protected override void OnClick(object sender, EventArgs eventArgs) 
    { 
     if (_onClick != null) 
     { 
      _onClick(sender, eventArgs); 
     } 
    } 

    private readonly string _name; 
    private readonly Action<object, EventArgs> _onClick; 

} 
3

Questo è quello che vorrei fare:

Retain link come una classe astratta, utilizzare una fabbrica per istanziare e passare in chiusura/metodo anonimo come parametro per il metodo di costruzione della fabbrica. In questo modo, puoi mantenere il tuo progetto originale con Link come una classe astratta, forzando l'implementazione attraverso la fabbrica e nascondendo comunque qualsiasi traccia concreta di Link all'interno della fabbrica.

Ecco qualche esempio di codice:

class Program 
{ 
    static void Main(string[] args) 
    { 

     Link link = LinkFactory.GetLink("id",() => 
     // This would be your onClick method. 
     { 
       // SetResponsePage(...); 
       Console.WriteLine("Clicked"); 
       Console.ReadLine(); 
     }); 
     link.FireOnClick(); 
    } 
    public static class LinkFactory 
    { 
     private class DerivedLink : Link 
     { 
      internal DerivedLink(String id, Action action) 
      { 
       this.ID = id; 
       this.OnClick = action; 
      } 
     } 
     public static Link GetLink(String id, Action onClick) 
     { 
       return new DerivedLink(id, onClick); 
     } 
    } 
    public abstract class Link 
    { 
     public void FireOnClick() 
     { 
      OnClick(); 
     } 
     public String ID 
     { 
      get; 
      set; 
     } 
     public Action OnClick 
     { 
      get; 
      set; 
     } 
    } 
} 

EDIT: In realtà, questo può essere un po 'più vicino a ciò che si vuole:

Link link = new Link.Builder 
{ 
    OnClick =() => 
    { 
     // SetResponsePage(...); 
    }, 
    OnFoo =() => 
    { 
     // Foo! 
    } 
}.Build("id"); 

Il bello è che utilizza un blocco di init, consentendoti di dichiarare il numero di implementazioni opzionali di azioni all'interno della classe Link che desideri.

Ecco la classe Link pertinente (con classe interna Costruttore sigillato).

public class Link 
{ 
    public sealed class Builder 
    { 
     public Action OnClick; 
     public Action OnFoo; 
     public Link Build(String ID) 
     { 
      Link link = new Link(ID); 
      link.OnClick = this.OnClick; 
      link.OnFoo = this.OnFoo; 
      return link; 
     } 
    } 
    public Action OnClick; 
    public Action OnFoo; 
    public String ID 
    { 
     get; 
     set; 
    } 
    private Link(String ID) 
    { 
     this.ID = ID; 
    } 
} 

Questa è vicino a quello che stai cercando, ma penso che possiamo fare un ulteriore passo avanti con argomenti predefiniti opzionali, una caratteristica C# 4.0. Diamo un'occhiata a l'esempio di dichiarazione di Link con argomenti con nome opzionali:

Link link = Link.Builder.Build("id", 
    OnClick:() => 
    { 
     // SetResponsePage(...); 
     Console.WriteLine("Click!"); 
    }, 
    OnFoo:() => 
    { 
     Console.WriteLine("Foo!"); 
     Console.ReadLine(); 
    } 
); 

Perchè questo è cool? Diamo un'occhiata alla nuova classe Link:

public class Link 
{ 
    public static class Builder 
    { 
     private static Action DefaultAction =() => Console.WriteLine("Action not set."); 
     public static Link Build(String ID, Action OnClick = null, Action OnFoo = null, Action OnBar = null) 
     { 
      return new Link(ID, OnClick == null ? DefaultAction : OnClick, OnFoo == null ? DefaultAction : OnFoo, OnBar == null ? DefaultAction : OnBar); 
     } 
    } 
    public Action OnClick; 
    public Action OnFoo; 
    public Action OnBar; 
    public String ID 
    { 
     get; 
     set; 
    } 
    private Link(String ID, Action Click, Action Foo, Action Bar) 
    { 
     this.ID = ID; 
     this.OnClick = Click; 
     this.OnFoo = Foo; 
     this.OnBar = Bar; 
    } 
} 

All'interno del Generatore di classe statica, v'è un metodo di costruzione fabbrica che prende in 1 parametro richiesto (L'ID) e 3 parametri opzionali, OnClick, OnFoo e OnBar. Se non vengono assegnati, il metodo factory fornisce loro un'implementazione predefinita.

Quindi, negli argomenti dei parametri del costruttore per Link, è necessario solo implementare i metodi necessari, altrimenti utilizzeranno l'azione predefinita, che potrebbe essere nulla.

Lo svantaggio, tuttavia, è nell'esempio finale, la classe Link non è astratta. Ma non può essere istanziato al di fuori dell'ambito della classe Link, perché il suo costruttore è privato (Forzando l'uso della classe Builder per istanziare Link).

Si potrebbe anche spostare direttamente i parametri facoltativi nel costruttore di Link, evitando la necessità di una fabbrica del tutto.

+0

Questa è la risposta di 3 anni fa, ma ho solo pensato di menzionare nella tua risposta finale che potresti accorciare un po 'la linea di ritorno: return new Link (ID, OnClick ?? DefaultAction, OnFoo ?? DefaultAction, OnBar ?? DefaultAction); –

Problemi correlati