studio - ¿Hay alguna forma de crear eventos indexados en C#(o alguna solución)?
eventos y delegados c# (10)
¿Qué hay de implementar INotifyPropertyChanged en INotifyPropertyChanged lugar?
Y entonces...
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void OnSourcePropertyChanged(Object sender, PropertyChangedEventArgs eventArgs)
{
if (eventArgs.PropertyName == "InterestingName")
{
// TODO:
}
}
El título es confuso. Déjame aclarar un poco:
Me gustaría proporcionar eventos que dependen de un parámetro para que un observador pueda decidir recibir eventos si algo le sucede a un "id" específico. Podría verse así:
public event EventHandler Foo (string id);
Soy consciente de que esta sintaxis es incorrecta en .NET 3.5, y también soy consciente de que esta idea presenta un problema adicional (por ejemplo, ¿cómo administramos la desuscripción?).
¿Cómo debo evitar este problema? Pensé en usar algo como:
public EventHandler Foo (string id);
que es al menos sintaxis legal y podría funcionar, pero todavía no me parece muy bien.
Edición: No estoy preguntando acerca de pasar argumentos a la función de devolución de llamada. Mi idea es más como esta:
class Bleh
{
public event EventHandler Foo (string index);
private void RaiseEvents() // this is called by a timer or whatever
{
Foo["asdf"] (this, EventArgs.Empty); // raises event for all subscribers of Foo with a parameter of "asdf"
Foo["97"] (this, EventArgs.Empty); // same for all "97"-subscribers
// above syntax is pure fiction, obviously
}
}
// subscribe for asdf events via:
Bleh x = new Bleh ();
x.Foo["asdf"] += (s, e) => {};
Explicación
Como probablemente te estarás preguntando por qué intento hacer esto, te explicaré mi situación. Tengo una clase que proporciona posiciones de ciertos objetos (cada uno de ellos identificado por alguna cadena de ID).
En lugar de proporcionar un event EventHandler<PositionChangedEventArgs>
que se genera para CUALQUIER cambio posicional, me gustaría tener un evento para cada objeto (al que se accede mediante un índice), para que los observadores puedan escuchar los eventos solo para una ID específica.
Acabo de empezar a usar Rx Framework y es brillante. Creo que podría ser lo que estás buscando.
http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx
La suscripción y la no suscripción se manejan en el marco. Se le ha llamado LINQ a los eventos. Es el ''dual matemático'' de IEnumerable.
Saludos, -jc
Básicamente encontré una manera más o menos elegante de resolver este problema:
Usa un diccionario de identificaciones para eventos. Acceso para agregar / eliminar escuchas a través de métodos.
// ignore threadsafety and performance issues for now.
private Dictionary<string, EventHandler> _Events = new Dictionary<string, EventHandler> ();
private void AddId (string id)
{
_Events[id] = delegate {
};
}
public void Subscribe (string id, EventHandler handler)
{
_Events[id] += handler;
}
public void Unsubscribe (string id, EventHandler handler)
{
_Events[id] -= handler;
}
private void Raise (string id)
{
_Events[id] (this, new EventArgs ());
}
static void Main (string[] args)
{
var p = new Program ();
p.AddId ("foo");
p.Subscribe ("foo", (s, e) => Console.WriteLine ("foo"));
p.Raise ("foo");
p.AddId ("bar");
p.Subscribe ("bar", (s, e) => Console.WriteLine ("bar 1"));
p.Subscribe ("bar", (s, e) => Console.WriteLine ("bar 2"));
p.Raise ("bar");
Console.ReadKey ();
}
Creo que las http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx son exactamente lo que estás buscando.
La idea * es esta:
Primero, defina una clase que se derive de EventArgs
e incluya la información que desea (en particular, cualquier "índice" que tenga en mente). Algo como esto:
public class IndexedEventArgs : EventArgs {
public string Index { get; private set; }
public IndexedEventArgs(string index) {
Index = index;
}
// ...
}
A continuación, para la clase que generará eventos, implemente un evento único utilizando EventHandler<TEventArgs>
y esta clase que acaba de definir. Dentro de esta definición de clase, cree un objeto que implemente IObservable
siguiente manera:
public class ClassWithIndexedEvents {
public event EventHandler<IndexedEventArgs> IndexedEvent;
public IObservable Events { get; private set; }
public ClassWithIndexedEvents() {
// yeah, this feels a little weird, but it works
Events = Observable.FromEvent<IndexedEventArgs>(this, "IndexedEvent");
}
// ...
}
Ahora, en el código en el que desea suscribirse solo a los eventos que coinciden con un determinado índice, puede filtrar su propiedad Events
de la misma forma que lo haría con un IEnumerable
:
// code mangled to fit without scrolling
public IDisposable CreateSubscription(
string eventIndex,
Action<IEvent<IndexedEventArgs>> handler) {
return Events.Where(e => e.Index == eventIndex).Subscribe(handler);
}
Observe que el método Subscribe
devuelve un objeto IDisposable
; esta es su clave para anular la suscripción posterior del evento filtrado al que acaba de suscribirse. El código es bastante obvio:
var fooSubscription = objectWithIndexedEvents.CreateSubscription(
"foo",
e => DoSomething(e)
);
// elsewhere in your code
fooSubscription.Dispose();
* Descargo de responsabilidad: estoy escribiendo todo este código más o menos de la memoria de cómo funciona Rx; No lo he probado, ya que no tengo Rx instalado en la máquina que estoy usando actualmente. Puedo revisar mañana en una máquina diferente para asegurarme de que todo está escrito correctamente; por ahora debería al menos servir como ilustración para darle una idea de cómo funciona Rx. Para obtener más información, siempre puedes consultar los tutoriales de Rx en línea.
Debe usar una clase derivada de EventArgs que incluya el ID, y luego usar EventHandler<IdEventArgs>
o lo que sea:
public class IdEventArgs : EventArgs
{
private readonly string id;
public string Id { get { return id; } }
public IdEventArgs(string id)
{
this.id = id;
}
}
public event Eventhandler<IdEventArgs> Foo;
Cuando levante el evento, tendrá que crear una instancia de IdEventArgs
, y luego el suscriptor podrá examinar eso y decidir qué hacer con él.
He preparado un ejemplo completo. Puedes usarlo de esta manera:
eventsSubscriptions["1"].EventHandler = new EventHandler(this.Method1);
eventsSubscriptions["2"].EventHandler = new EventHandler(this.Method2);
eventsSubscriptions["3"].EventHandler = new EventHandler(this.Method3);
Boss Boss1 = new Boss("John Smith");
Boss Boss2 = new Boss("Cynthia Jameson");
Employed Employed1 = new Employed("David Ryle");
Employed Employed2 = new Employed("Samantha Sun");
Employed Employed3 = new Employed("Dick Banshee");
// Subscribe objects to Method 1
eventsSubscriptions["1"].Subscribe(Boss1);
eventsSubscriptions["1"].Subscribe(Employed1);
// Subscribe objects to Method 2
eventsSubscriptions["2"].Subscribe(Boss2);
eventsSubscriptions["2"].Subscribe(Employed2);
// Subscribe objects to Method 3
eventsSubscriptions["3"].Subscribe(Employed3);
Luego, puedes llamar a los métodos de RaiseAllEvents (), y esta es la salida de la consola:
- Método 1 planteado con el jefe John Smith
- Método 1 planteado con el empleado David Ryle
- Método 2 planteado con el jefe Cynthia Jameson
- Método 2 planteado con la empleada Samantha Sun
- Método 3 planteado con el empleado Dick Banshee
En las siguientes líneas, pegaré el código de todas las clases involucradas. Con un poco de paciencia y copiar / pegar, podrás probarlo = P Espero que te ayude.
--- El código ---
Principal
namespace MyExample
{
public class Program
{
static void Main(string[] args)
{
SomeExampleClass someExampleInstance = new SomeExampleClass();
someExampleInstance.SuscribeObjects();
someExampleInstance.RaiseAllEvents();
Console.ReadLine();
}
}
}
Persona de clase
namespace MyExample
{
public abstract class Person
{
protected string name;
public Person(string name)
{
this.name = name;
}
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public override string ToString()
{
return (this.GetType().Name + " " + name);
}
}
}
Jefe de clase
namespace MyExample
{
public class Boss : Person
{
public Boss(string name)
: base(name)
{ }
}
}
Empleado
namespace MyExample
{
public class Employee : Person
{
public Employee(string name)
: base(name)
{ }
}
}
Class SomeExampleClass
namespace MyExample
{
public class SomeExampleClass
{
private EventsSubscriptions eventsSubscriptions = new EventsSubscriptions();
private void Method1(object sender, System.EventArgs e)
{
Console.WriteLine("Method 1 raised with " + sender.ToString());
}
private void Method2(object sender, System.EventArgs e)
{
Console.WriteLine("Method 2 raised with " + sender.ToString());
}
private void Method3(object sender, System.EventArgs e)
{
Console.WriteLine("Method 3 raised with " + sender.ToString());
}
public void SuscribeObjects()
{
eventsSubscriptions["1"].EventHandler = new EventHandler(this.Method1);
eventsSubscriptions["2"].EventHandler = new EventHandler(this.Method2);
eventsSubscriptions["3"].EventHandler = new EventHandler(this.Method3);
Boss Boss1 = new Boss("John Smith");
Boss Boss2 = new Boss("Cynthia Jameson");
Employee Employee1 = new Employee("David Ryle");
Employee Employee2 = new Employee("Samantha Sun");
Employee Employee3 = new Employee("Dick Banshee");
// Method 1
eventsSubscriptions["1"].Subscribe(Boss1);
eventsSubscriptions["1"].Subscribe(Employee1);
//// Method 2
eventsSubscriptions["2"].Subscribe(Boss2);
eventsSubscriptions["2"].Subscribe(Employee2);
//// Method 3
eventsSubscriptions["3"].Subscribe(Employee3);
}
public void RaiseAllEvents()
{
eventsSubscriptions.RaiseAllEvents();
}
}
}
Eventos de claseSuscripciones
namespace MyExample
{
public class EventsSubscriptions
{
private Dictionary<string, Subscription> subscriptions = new Dictionary<string, Subscription>();
public Subscription this[string id]
{
get
{
Subscription subscription = null;
subscriptions.TryGetValue(id, out subscription);
if (subscription == null)
{
subscription = new Subscription();
subscriptions.Add(id, subscription);
}
return subscription;
}
}
public void RaiseAllEvents()
{
foreach (Subscription subscription in subscriptions.Values)
{
Subscription iterator = subscription;
while (iterator != null)
{
iterator.RaiseEvent();
iterator = iterator.NextSubscription;
}
}
}
}
}
Suscripción de clase
namespace MyExample
{
public class Subscription
{
private object suscribedObject;
private EventHandler eventHandler;
private Subscription nextSubscription;
public object SuscribedObject
{
set
{
suscribedObject = value;
}
}
public EventHandler EventHandler
{
set
{
eventHandler = value;
}
}
public Subscription NextSubscription
{
get
{
return nextSubscription;
}
set
{
nextSubscription = value;
}
}
public void Subscribe(object obj)
{
if (suscribedObject == null)
{
suscribedObject = obj;
}
else
{
if (nextSubscription != null)
{
nextSubscription.Subscribe(obj);
}
else
{
Subscription newSubscription = new Subscription();
newSubscription.eventHandler = this.eventHandler;
nextSubscription = newSubscription;
newSubscription.Subscribe(obj);
}
}
}
public void RaiseEvent()
{
if (eventHandler != null)
{
eventHandler(suscribedObject, new System.EventArgs());
}
}
}
}
Implementado como una sola clase, con una API simple.
// subscribe to an event
eventsource.AddHandler( "foo", MyEventHandler );
// unsubscribe
eventsource.RemoveHandler( "foo", MyEventHandler );
// raise event for id
eventsource.RaiseEvent( "foo" );
public class EventSource
{
Dictionary<string,List<EventHandler>> handlers = new Dictionary<string,List<EventHandler>>();
public void AddHandler( string id, EventHandler handler )
{
if (!handlers.ContainsKey( id )) {
handlers[id] = new List<EventHandler>();
}
handlers[id].Add( handler );
}
public void RemoveHandler( string id, EventHandler handler )
{
if (handlers.ContainsKey( id )) {
handlers[id].Remove( handler );
}
}
public void RaiseEvent( string id )
{
if (handlers.ContainsKey( id )) {
foreach( var h in handlers[id] ) {
h( this, EventArgs.Empty );
}
}
}
}
No sé si usaría eventos en este caso, pero no estoy seguro de que ese sea el mayor problema.
Si está tratando de controlar a los suscriptores, creo que es mejor dejar que los suscriptores se encarguen del filtrado. Solo ellos saben qué es lo que realmente quieren filtrar, por lo que colocar el código de filtrado en la clase que emite los eventos parece subóptimo.
Permítame intentar aclarar un poco, si puedo ... El código para determinar si el destinatario A
preocupa por un evento del emisor B
vive en algún lugar. Puede parecer que tiene sentido ponerlo en B
Sin embargo, el problema surge cuando te das cuenta de que también debes considerar a los destinatarios C
, D
y E
Pueden tener una lógica complicada para determinar lo que les importa (e incluso puede cambiar con el tiempo). Poner toda esta lógica en nuestro emisor ( B
) va a ser una clase grande y torpe que es difícil de usar.
Otra opción es que A
tenga la lógica de si desea o no el evento internamente. Esto localiza la lógica en A
, mantiene a B
limpio y es fácil de consumir para todos los demás. Sin embargo, la desventaja de esto es que la lógica de suscripción no es utilizable por C
si resulta que es la misma.
Pero si realmente lo pensamos, tenemos tres cosas sucediendo aquí: un evento que se está emitiendo, el filtrado de eventos a los destinatarios y la recepción / reacción a los eventos. El principio de responsabilidad única nos dice que una clase solo debe tener responsabilidad, una razón para cambiar. Al incluir la lógica de filtrado en A
o B
, cualquiera que la obtenga ahora tiene dos responsabilidades y dos razones para cambiar.
Entonces, lo que me sentiría tentado a hacer en este caso es crear otra clase, Q
, que contenga la lógica para el filtrado de eventos. Ahora, ni A
ni B
obtienen la lógica adicional en su código. C
no tiene que volver a implementarlo. Y, como beneficio adicional, ahora podemos unir fácilmente varios filtros para obtener un comportamiento de filtro complejo basado en componentes muy simples.
Podrías hacer algo como esto:
public class Foo
{
public class Bar
{
public event EventHandler PositionChanged;
internal void RaisePositionChanged()
{
var handler = PositionChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
private Dictionary<string, Bar> m_objects;
public Bar this[string id]
{
get
{
if (!m_objects.ContainsKey(id))
m_objects.Add(id, new Bar());
return m_objects[id];
}
}
private void RaisePositionChanged(string id)
{
Bar bar;
if (m_objects.TryGetValue(id, out bar))
bar.RaisePositionChanged();
}
}
Entonces, para suscribir un evento, sería tan simple como esto:
Foo foo = new Foo();
foo["anId"].PositionChanged += YourHandler;
Quieres decir algo como
public class EventArgs<T> : EventArgs
{
private T _value;
public T Value
{
get { return this._value; }
protected set { this._value = value; }
}
public EventArgs(T value)
{
this.Value = value;
}
}
// ...
public event EventHandler<EventArgs<string>> Foo;
?