2011-01-22 24 views
22

Estoy en el proceso de escribir una implementación de Wicket C# para profundizar mi comprensión de C# y Wicket. Uno de los problemas con los que nos encontramos es que Wicket hace un uso intensivo de las clases internas anónimas, y C# no tiene clases internas anónimas.Clases internas anónimas en C#

Así, por ejemplo, en Wicket, se define un enlace como este:

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

Desde Link es una clase abstracta, que obliga al implementador para poner en práctica un método onClick.

Sin embargo, en C#, ya que no hay clases internas anónimas, no hay forma de hacerlo. Como alternativa, puede usar eventos como este:

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

Por supuesto, hay un par de inconvenientes con esto. En primer lugar, puede haber múltiples manejadores de Click, que pueden no ser atractivos. Tampoco obliga al implementador a agregar un controlador de Click.

Otra opción podría ser simplemente tener una propiedad de cierre como esto:

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

Esto resuelve el problema de tener muchos manipuladores, pero todavía no fuerza el implementador para agregar el controlador.

Entonces, mi pregunta es, ¿cómo se emula algo así en idiomático C#?

+1

No veo una clase interna anónima en el ejemplo que usted dio. Si desea que los implementadores de su clase abstracta implementen siempre algunos métodos, puede crear un método abstracto en la clase o implementar una interfaz. – tenor

+2

@tenor, hay una clase anónima en línea definida que hereda de 'Enlace' y anula el método' onClick'. A diferencia de Java, C# no admite clases anónimas para derivar de un tipo de usuario dado. –

+0

@Darin Dimitrov, gracias por señalar eso. Estaba buscando una verdadera clase "interna/anidada". El ejemplo proporcionado se parece más a una clase anónima que se deriva de una clase existente, al menos en la jerga de C#. – tenor

Respuesta

17

Puede hacer que el delegado sea parte del constructor de la clase Link. De esta forma el usuario tendrá que agregarlo.

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

} 

A continuación, se crea una instancia de esta manera:

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

Sí, eso también funcionaría con parámetros nombrados. – cdmckay

1

Empecé esto antes buena respuesta de @ meatthew - Haría casi exactamente el mismo, salvo - excepto que me gustaría empezar con una clase base abstracta - por lo que si no desea seguir la ruta de una implementación anónima, también podrá hacerlo.

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

Esto es lo que haría:

Permanencia Enlace como una clase abstracta, utilizan una fábrica instanciarlo y procesar el cierre de método/anónimo como un parámetro para el método de aumento de la fábrica. De esta manera, puede mantener su diseño original con Link como una clase abstracta, forzando la implementación a través de la fábrica, y aún ocultando cualquier rastro concreto de Link dentro de la fábrica.

Aquí es un código de ejemplo:

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: En realidad, esto puede ser un poco más cerca de lo que quiere:

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

La belleza es que utiliza un bloque init, permitiéndote declarar tantas implementaciones opcionales de acciones dentro de la clase Link como quieras.

Aquí está la clase de enlace relevante (con clase interna de Builder sellada).

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

Esto se acerca a lo que estás buscando, pero creo que podemos dar un paso más allá con argumentos con nombre opcional, una característica de C# 4.0. Veamos la declaración de ejemplo de Enlace con argumentos con nombre opcionales:

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

¿Por qué es genial? Vamos a ver la nueva clase Enlace:

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

Dentro del constructor de clase estática, existe un método de construcción de la fábrica que lleva en 1 parámetro requerido (El ID) y 3 parámetros opcionales, onclick, OnFoo y enclavar. Si no están asignados, el método de fábrica les da una implementación predeterminada.

Por lo tanto, en los argumentos del parámetro del constructor para el Enlace, solo se requiere implementar los métodos que necesita, de lo contrario utilizarán la acción predeterminada, que podría no ser nada.

El inconveniente, sin embargo, es en el ejemplo final, la clase de enlace no es abstracta. Pero no se puede instanciar fuera del alcance de la clase Link, porque su constructor es privado (Forzando el uso de la clase Builder para instanciar Link).

También podría mover los parámetros opcionales al constructor de Link directamente, evitando la necesidad de una fábrica en conjunto.

+0

Esta es la respuesta de hace 3 años, pero pensé que mencionaría en su respuesta final que podría acortar un poco la línea de retorno: return new Link (ID, OnClick ?? DefaultAction, OnFoo ?? DefaultAction, OnBar ?? DefaultAction); –

Cuestiones relacionadas