0

I have a class that has a bunch of properties of certain type , this type has 3 events and I want to subscribe to this events on my class constructor, but avoiding typing each property name 3 times, is this possible?

Something like iterate trough all the properties of this type and add the event handlers?

internal class myValues {
    public myValues () {
        // Want to avoid hardcoding all my Properties here, and loop trough them all
        Test1.AnyChanged += OnMetricChanged;
        Test1.ValueChanged += OnMetricValueChanged;
        Test1.DeltaChanged += OnMetricDeltaChanged;
        ...
        ...
        // I can go trough all of them, but not sure how to add the handlers here
        var properties = typeof(myValues).GetProperties().Where(p => p.PropertyType == typeof(Metric));
        foreach (var p in properties) {
                Debug.WriteLine(p.Name );
        }

    }
    public Metric Test1 { get; private set; }
    public Metric Test2 { get; private set; }
    public Metric Test3 { get; private set; }
    public Metric Test4 { get; private set; }

}


Rand Random
  • 7,300
  • 10
  • 40
  • 88
Smoke
  • 141
  • 1
  • 10
  • This can be solved by using reflection: https://stackoverflow.com/questions/824802/how-to-get-all-public-both-get-and-set-string-properties-of-a-type – Eddi Jun 14 '23 at 11:06
  • 2
    and how do you get the handlers? Why is `AnyChanged` mapped to `OnMetricChanged` while `ValueChanged ` is mapped to `OnMetricValueChanged `? How do you map an event-handler to an event? I can't see any logic here. – MakePeaceGreatAgain Jun 14 '23 at 11:07
  • @MakePeaceGreatAgain Those are the handlers I want to use, they are already defined in the class (not shown there). and those are the 3 events of the type, just need to repeat that code for all my properties – Smoke Jun 14 '23 at 11:12
  • 1
    but how do you know which handler to use for which event? Or do all your projects have the exact same three events and handlers? – MakePeaceGreatAgain Jun 14 '23 at 11:13
  • They all use the same – Smoke Jun 14 '23 at 11:14
  • 1
    @Eddi i was able to get all the properties indeed, but not sure how to add the handler once i get them var props= typeof(myClass).GetProperties().Where(p => p.PropertyType == typeof(Metric)) – Smoke Jun 14 '23 at 11:15
  • you should add what you've tried into your question together with what problems you have with that code – MakePeaceGreatAgain Jun 14 '23 at 11:16
  • 1
    FYI - the convention for naming event handlers is not to start with "On" as that is used for the method that actually raise the event in the source class. – Enigmativity Jun 14 '23 at 11:42
  • 1
    This sounds like a solution looking for an answer. You would maybe better looking at the mediator pattern (and MediatR nuget), which I think is a better fit for your problem. – Neil Jun 14 '23 at 11:42

2 Answers2

2

You could apply method extraction to property subscription and then iterate over properties via reflection to subscribe each of them

var myValues = new myValues();

public class Metric
{
    public event EventHandler AnyChanged;
    public event EventHandler ValueChanged;
    public event EventHandler DeltaChanged;
}

public class myValues
{
    public myValues()
    {
        var properties = typeof(myValues).GetProperties().Where(p => p.PropertyType == typeof(Metric));
        foreach (var p in properties)
        {
            var metric = (Metric)p.GetValue(this);
            SubscribeMetric(metric);
            
            Console.WriteLine($"Subscribed {p.Name}");
        }
    }

    private void SubscribeMetric(Metric metric)
    {
        metric.AnyChanged += OnMetricChanged;
        metric.ValueChanged += OnMetricValueChanged;
        metric.DeltaChanged += OnMetricDeltaChanged;
    }

    private void OnMetricDeltaChanged(object? sender, EventArgs e)
    {
    }

    private void OnMetricValueChanged(object? sender, EventArgs e)
    {
    }

    private void OnMetricChanged(object? sender, EventArgs e)
    {
    }

    public Metric Test1 { get; set; } = new();
    public Metric Test2 { get; set; } = new();
    public Metric Test3 { get; set; } = new();
    public Metric Test4 { get; set; } = new();
}
Yehor Androsov
  • 4,885
  • 2
  • 23
  • 40
  • Thats exactly what i was looking for, thanks a lot! I didnt realize I could get the actual property as var metric = (Metric)p.GetValue(this); Thanks a LOT – Smoke Jun 14 '23 at 11:44
1

I would extend the setter and attach my events there, instead of

internal class myValues {
    public Metric Test1 { get; private set; }
    public Metric Test2 { get; private set; }
    public Metric Test3 { get; private set; }
    public Metric Test4 { get; private set; }
}

I would do this

internal class myValues {
    private Metric _test1;
    private Metric _test2;
    private Metric _test3;
    private Metric _test4;

    public Metric Test1 { get => _test1; private set => SetValue(ref _test1, value); }
    public Metric Test2 { get => _test2; private set => SetValue(ref _test2, value); }
    public Metric Test3 { get => _test3; private set => SetValue(ref _test3, value); }
    public Metric Test4 { get => _test4; private set => SetValue(ref _test4, value); }

    private void SetValue(ref Metric toSet, Metric value)
    {
         //unregister events from old value
         if (toSet is not null)
         {
            toSet.AnyChanged -= OnMetricChanged;
            toSet.ValueChanged -= OnMetricValueChanged;
            toSet.DeltaChanged -= OnMetricDeltaChanged;
         }

         toSet = value;
         if (toSet is not null)
         {
            toSet.AnyChanged += OnMetricChanged;
            toSet.ValueChanged += OnMetricValueChanged;
            toSet.DeltaChanged += OnMetricDeltaChanged;
         }
    }
}

a different Idea would be to create the following structure instead

//create an "Listener" interface
interface IMetricListener
{
    //some ideas how the method could look
    void AnyChanged(Metric sender, object value);
    void ValueChanged(Metric sender, object oldValue, object newValue);
    void DeltaChanged(Metric sender);
}

class Metric
{
     private IMetricListener _listener;
     
     //pass a "Listener" object to metric
     public Metric(IMetricListener listener)
     {
          _listener = listener;
     }

     public Metric()
     {

     }

     public void Foo()
     {
         //do stuff
         
         //if _listner isnt null, call a "change" method
         _listener?.AnyChanged(this);
     }
}

//Implement IMetricListener
class myValues : IMetricListener
{
    public void AnyChanged(Metric sender, object value) => { }
    public void ValueChanged(Metric sender, object oldValue, object newValue) => { }
    public void DeltaChanged(Metric sender) => { }

    public myValues () {
        //create metrics by passing this as a listener
        Test1 = new Metric(this);
        Test2 = new Metric(this);
        Test3 = new Metric(this);
        Test4 = new Metric(this);
    }
}
Rand Random
  • 7,300
  • 10
  • 40
  • 88
  • I dont want Metric to have any setter, although I wrote a private set there, in fact they are only read only, they have a public method to set values (it as a lot of logic) So yes, i see your logic will work but its a bit more effort that I wanted to avoid If possible – Smoke Jun 14 '23 at 11:40
  • 1
    @smoke - added different idea – Rand Random Jun 14 '23 at 11:46
  • @Smoke - private set or not makes no difference, I simply forgot it (added it now it latest edit) - https://dotnetfiddle.net/7Uih6I – Rand Random Jun 14 '23 at 11:49
  • Love the idea of the interface, thanks a lot for the feedback @Rand ! – Smoke Jun 14 '23 at 19:09