简体   繁体   中英

Elegant way to give a delegate properties

Suppose I have some event in a class:

 class SomeClass{
     event ChangedEventHandler SomeDoubleChanged;
     double SomeDouble;
 }

with:

 delegate void ChangedEventHandler(double d);

Now suppose I want to listen for change events on SomeDouble, but only want to trigger changes greater than delta . Now I could do something like

 SomeObject.SomeDoubleChanged += (d) => {if(abs(d-old_d) > delta){
                                  //do something     
                                 };

But I want my event to handle this, so in the best case I want to do something like:

 SomeObject.SomeDoubleChange += (delta, (d) => {});

and still allow:

 SomeObject.SomeDoubleChange += (d) => {};

The only way I only way I thought of implementing this, is to drop the whole event keyword and implement a container with += and -= operators, operating on the specified delegates. But in my opinion this is not a very elegant solution, as it gives the user of SomeClass the idea that SomeDoubleChanged is no event.

What would be the most elegant solution to this problem?

(I'd recommend not using the term "lambda" here given that you're also using lambda expressions. It sounds like you're interested in the change, ie the delta.)

You could create your own static methods to create appropriate delegates:

public static ChangedEventHandler FilterByDelta(double delta,
                                                ChangedEventHandler handler)
{
    double previous = double.MinValue;
    return d =>
    {            
        if (d - previous > delta)
        {
            handler(d);
        }
        // Possibly put this in the "if" block? Depends on what you want.
        previous = d;
    };
}

Then you can use:

SomeObject.SomeDoubleChange += Helper.FilterByDelta(5, d => ...);

Unfortunately you can't use extension methods on lambda expressions, which would make this easier.

You can write someting like this

SomeObject.SomeDoubleChange += (d) => DoWork(lambda, d, (d) => {});

private void DoWork(double minLambda, double currentValue, ChangedEventHandler handler)
{
  if(abs(currentValue - oldValue) > minLambda))
  {
     handler(currentValue);
  }
}

My favorite "elegant" way would be by using observables instead of events, especially, implemented with the help of Reactive Extensions . Imagine being able to do so:

class SomeClass
{
    public ObservableProperty<double> SomeDouble { get; private set; }
    public SomeClass()
    {
        SomeDouble = new ObservableProperty<double>();
    }
}

class Program
{
    static void Main(string[] args)
    {
        SomeClass someobject = new SomeClass();
        const double lam = 1.0;
        using (var sub = someobject.SomeDouble.Observable
            .TakeWhile((oldvalue, newvalue) => 
                Math.Abs(oldvalue - newvalue) > lam)
            .Subscribe(x => 
                Console.WriteLine("{0}\t{1}",x,someobject.SomeDouble.Value)))
        {
            someobject.SomeDouble.Value = 3.0;
            someobject.SomeDouble.Value = 2.0;
            someobject.SomeDouble.Value = 1.0;
            someobject.SomeDouble.Value = -1.0;
        }
    }
}

with the output

3       3
1       1
-1      -1

The lambda is here a parameter of the custom extension method TakeWhile .

The wrapper for observable properties would can look like that (possibly not quite complete):

/// Wrapper for properties that notify their change by means of an observable sequence
class ObservableProperty<T>
{
    T val;
    ISubject<T> sub = new Subject<T>();

    public T Value
    {
        get { return val; }
        set
        {
            val = value;
            sub.OnNext(value);
        }
    }

    public IObservable<T> Observable
    {
        get { return sub; }
    }
}

And the custom extension method as follows:

public static class Extensions
{
    /// Return distinct values from source while AdjacentCompare is true
    public static IObservable<TSource> TakeWhile<TSource>(this IObservable<TSource> source, Func<TSource, TSource, bool> AdjacentCompare)
    {
        return source.Scan((oldvalue, newvalue) =>
        {
            if (AdjacentCompare(oldvalue, newvalue))
                return newvalue;
            return oldvalue;
        }).DistinctUntilChanged();
    }
}

The helper class and the extension method can look comparatively lengthy, but those are generic, ie it doesn't matter how you compare your values, and what type they are (limited by the implementation, perhaps), and you get the rx benefits of controlling subscription lifetime easily, thread safety, and writing declarative code.

While searching some time ago I came across this library: ReactiveProperty

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM