what method classes anonyme c# java closures wicket anonymous-inner-class

c# - method - class anonyme java



Clases internas anĂ³nimas en C# (3)

Comencé esto antes de la buena respuesta de @ meatthew (haría casi lo mismo, excepto que empezaría con una clase base abstracta), de modo que si no deseara seguir la ruta de una implementación anónima, sería libre de hacerlo. eso también.

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

Estoy en el proceso de escribir una implementación de C # Wicket 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 que C # no tiene clases internas anónimas.

Entonces, por ejemplo, en Wicket, define un enlace como este:

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

Como Link es una clase abstracta, obliga al implementador a implementar un método onClick.

Sin embargo, en C #, ya que no hay clases internas anónimas, no hay manera de hacer esto. Como alternativa, podrías 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 varios controladores de clic, que pueden no ser geniales. Tampoco obliga al implementador a agregar un controlador Click.

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

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

Esto resuelve el problema de tener muchos controladores, pero aún así no obliga al implementador a agregar el controlador.

Entonces, mi pregunta es, ¿cómo emulas algo como esto en C # idiomático?


Esto es lo que yo haría:

Conserve Link como una clase abstracta, use una Factory para crear una instancia y pase su método de cierre / anónimo como parámetro para el método de construcción de Factory. 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í hay 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 estar un poco más cerca de lo que quieres:

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

La belleza es que utiliza un bloque de inicio, lo que le permite declarar tantas implementaciones opcionales de acciones dentro de la clase Link como desee.

Aquí está la clase de enlace relevante (con la clase interna del constructor sellado).

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 está cerca de lo que está buscando, pero creo que podemos ir un paso más allá con argumentos con nombre opcionales, una característica de C # 4.0. Veamos la declaración de ejemplo de enlace con argumentos nombrados opcionales:

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

¿Por qué esto es genial? Echemos un vistazo a la nueva clase de 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 Generador de clases estáticas, hay un método de construcción de fábrica que toma 1 parámetro requerido (La ID) y 3 parámetros opcionales, OnClick, OnFoo y OnBar. Si no están asignados, el método de fábrica les da una implementación predeterminada.

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

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

También puede mover los parámetros opcionales al constructor de Link directamente, evitando la necesidad de una fábrica por completo.


Puede hacer que el delegado sea parte del constructor de la clase Link. De esta manera el usuario tendrá que añadirlo.

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

Entonces creas una instancia de esta manera:

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