Suppose a class in itself encapsulates some type that is stored in a private field.

This type has some event and I would like to be able to subscribe to the event from the outside.

Tell me, the only solution is to create a public event, then subscribe inside a class for a private field event, and already trigger a public event in the handler, or is there some special syntax?

    2 answers 2

    Create your public event, in its implementations add and remove, sign the subscriber directly with a private object event:

    using System; using System.Timers; class Program { static void Main() { MyTimer myTimer = new MyTimer(); myTimer.Tick += (o, e) => Console.WriteLine("Hello!"); Console.ReadLine(); } } class MyTimer { Timer timer = new Timer(1000); public MyTimer() { timer.Start(); } public event ElapsedEventHandler Tick { add => timer.Elapsed += value; remove => timer.Elapsed -= value; } } 

    If you strongly want to - you can expand the idea to something like this, now the private object will not “leak”, and the event signature can be changed:

     class MyTimer { Timer timer = new Timer(333); public MyTimer() { timer.Start(); } Dictionary<ElapsedEventHandler, Action> handlers = new Dictionary<ElapsedEventHandler, Action>(); public event Action Tick { add { if (value == null) return; ElapsedEventHandler h = (o, e) => value.Invoke(); handlers[h] = value; timer.Elapsed += h; } remove { if (value == null) return; ElapsedEventHandler h = handlers.FirstOrDefault( pair => pair.Value == value).Key; if (h == null) return; timer.Elapsed -= h; handlers.Remove(h); } } } 

    Please note that the key and the value in the dictionary cannot be changed in places; otherwise, if you re-subscribe the same method, you will not be able to unsubscribe from the created lambda they will be the same)

    You can test this code:

     class Program { static void Main() { MyTimer myTimer = new MyTimer(); myTimer.Tick += MyTimer_Tick1; Console.ReadLine(); myTimer.Tick += MyTimer_Tick2; Console.ReadLine(); myTimer.Tick += MyTimer_Tick2; Console.ReadLine(); myTimer.Tick -= MyTimer_Tick2; Console.ReadLine(); myTimer.Tick -= MyTimer_Tick2; Console.ReadLine(); myTimer.Tick -= MyTimer_Tick2; Console.ReadLine(); } private static void MyTimer_Tick2() => Console.WriteLine(2); private static void MyTimer_Tick1() => Console.WriteLine(1); } 

    Repeated subscriptions and unsubscriptions work correctly; also unsubscribing an unsigned method does not throw exceptions.

    If you pervert a little more, it may even be possible to figure out a generic class for converting different delegates, which encapsulates the dictionary and all the dirty work, but, for me, this is already a dash


    I took my own interest, sketched a class, but for some reason the answer is not working, can someone in the comments tell me how to fix it ...

     class DelegateConverter<TIn, TOut> where TIn : class where TOut : class { Dictionary<TOut, TIn> handlers = new Dictionary<TOut, TIn>(); Func<TIn, TOut> _convertFunc; public DelegateConverter(Func<TIn, TOut> convertFunc) { _convertFunc = convertFunc; } public TOut Add(TIn d) { if (d == null) return null; TOut h = _convertFunc(d); handlers[h] = d; return h; } public TOut Remove(TIn d) { if (d == null) return null; TOut h = null; foreach (var pair in handlers) if (pair.Value == d) h = pair.Key; //TOut h = handlers.FirstOrDefault(pair => pair.Value == d).Key; if (h == null) return null; handlers.Remove(h); return h; } } 

    Here in the Remove method, for some reason, always h == null ...

    The rewritten method of our MyTimer :

     class MyTimer { Timer timer = new Timer(333); public MyTimer() { timer.Start(); } DelegateConverter<Action, ElapsedEventHandler> dConverter = new DelegateConverter<Action, ElapsedEventHandler>(v => (o, e) => v.Invoke()); public event Action Tick { add => timer.Elapsed += dConverter.Add(value); remove => timer.Elapsed -= dConverter.Remove(value); } } 

    We test the old

    • 2
      Not the best solution: a private variable will "leak" in the first argument of the event. - Pavel Mayorov
    • one
      @PavelMayorov, well, then just subscribe and overexcite. Here, the minus is that it is not clear how to change the type of delegate, so that you can unsubscribe without ... - Andrey NOP
    • It seems to me, or compared to the classic event routing through an internal handler, the code has increased and the reliability is less? Well, the question about the broken reply, the question on a good one needs to be arranged separately. - rdorn
    • @rdorn, of course, the classic version is simpler and more reliable, and I would prefer it. But since the author asks ... So to say, as an optional task, why not have fun? - Andrei NOP
    • Try with two symmetric dictionaries. The last time I did a two-way converter, it turned out quite reliably and efficiently. - rdorn

    If it is necessary to preserve the privacy of the internal instance, then yes, the only reliable way is to create an independent external event and excite it in the internal processor. Of course, you can come up with a number of exotic options and solve problems arising heroically, except that it will not reduce the amount of code, and reliability can be seriously affected.

    This method has other advantages:

    • You are not required to use a delegate of the same type as the primary source of the event. This gives some additional flexibility in transferring the required parameters to external processors.

    • It is also not a problem to add private logic to handle the original event. This is necessary, for example, in WinForms, for downward routing of events (from child controls to parent). Of course, this may not be necessary either immediately or later, but why lay the time bomb with his own hands and wait for jerking or not.


    If privacy is not necessary (i.e., our code does not have a hard dependency on the state of the internal object):

    • You can use the solution from the next answer (in the first edition). Standard delegates of events with the first parameter accept the link to the object that initiated the event, and with this link you can get access to the object itself, so this option allows you to safely bypass the privacy of the source object.

    • I prefer to just replace the field with a read-only public property, since privacy is not important, and subscribe directly.

    • Bringing a private field to the public may also not be good, except for subscribing to an event, an external code can call the method of this object or somehow change its internal state, which in turn can break the logic of your class ... So it's better to forward event - Andrew NOP
    • @ Andrey I specifically clarified that this is the case when privacy does not matter, and this already implies unlimited access. This is rarely necessary, but if necessary, for me it is better this way. - rdorn
    • @ Andrey now shouldn’t be like misunderstandings - rdorn