Click here to Skip to main content
15,894,343 members
Please Sign up or sign in to vote.
1.00/5 (2 votes)
See more:
Hey,

I got many classes that have a prop and need some sort of mechanism to wait for the property to change.
Imagine this:

class A
{
    public event Action OnChanged;
    public int Data { get; set; }
    public void DoWork() { /* after some time will update "Data" property */ }
}
class B
{
    public event Action OnChanged;
    public int Data { get; set; }
    public void DoWork() { /* after some time will update "Data" property */ }
}
class C
{
    public event Action OnChanged;
    public int Data { get; set; }
    public void DoWork() { /* after some time will update "Data" property */ }
}


Now I will need to create more of these classes in the future and I need some generic method/extension to wait for the property "Data" change.
I know I can do this with events and then using Thread.Sleep, but it's not the best way of coding since sleeping takes up a thread.
Another way was to use the TaskCompletionSource like this:

// this is for class A only... I would need to copy paste this for B and C
static class Extension
    {
        public static Task<object> ToTask(this A manager)
        {
            TaskCompletionSource<object> taskCompletionSource = new TaskCompletionSource<object>(TaskCreationOptions.AttachedToParent);

            Action onChanged = null;

            onChanged = () =>
            {
                manager.OnChanged -= onChanged;
                taskCompletionSource.SetResult(null);
            };

            manager.OnChanged += onChanged;

            return taskCompletionSource.Task;
        }
    }


The above allows the have this in the main method:

static void Main(string[] args)
{
    A instance = new A();
    instance.DoWork().ToTask().Wait();

}


...But then I would have to write the extension for every new class i create, which is not something I want. Anyone know of a way how to generalize this?

Thanks!

What I have tried:

Check the code above - tried quiet a few things here.
Posted
Updated 16-May-17 4:03am
v2

Three obvious options:

1) Derive from a base class which defines the event, and declare your extension method to take an instance of that base class.
abstract class ManagerBase
{
    public event EventHandler Changed;
    
    protected void OnChanged()
    {
        Changed?.Invoke(this, EventArgs.Empty);
    }
}

class A : ManagerBase { ... }
class B : ManagerBase { ... }
class C : ManagerBase { ... }

...

public static Task ToTask(this ManagerBase manager) { ... }


2) Implement an interface which defines the event, and declare your extension method to take an instance of that interface:
interface IManager
{
    event EventHandler Changed;
}

class A : IManager
{
    public event EventHandler Changed;
    
    private void OnChanged()
    {
        Changed?.Invoke(this, EventArgs.Empty);
    }
    
    ...
}

...

public static Task ToTask(this IManager manager) { ... }


3) Pass in the methods to attach and detach the event handler as actions:
public static Task ToTask<T>(this T manager, Action<T, EventHandler> addHandler, Action<T, EventHandler> removeHandler)
{
    var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.AttachedToParent);
    
    EventHandler onChanged = null;
    
    onChanged = (sender, args) =>
    {
        removeHandler((T)sender, onChanged);
        taskCompletionSource.TrySetResult(true);
    };
    
    addHandler(manager, onChanged);
    
    return taskCompletionSource.Task;
}
 
Share this answer
 
Comments
Ralf Meier 16-May-17 10:11am    
@Richard:
because I'm not so familiar with C# : what means 'Changed?' - here I mean the Questionmark ...
Richard Deeming 16-May-17 10:15am    
It's the "null conditional operator", also known as the "null propagation operator", which was introduced in C# 6 (Visual Studio 2015).

C# : The New and Improved C# 6.0[^]
Clean event handler invocation with C# 6[^]
MK-Gii 16-May-17 11:40am    
Thanks. Good explanation and fine solution.
I'm not sure what you try to achieve ...
1st:
if you have things which are common to many classes you could code them into one baseClass and let all other classes derive from it
2nd:
you don't need to code a Task to check if a Property of your class changes. This could very easily done inside the Setter(-Method) of your Property - here you can raise the Event

VB
Property Test As Double
     Get
         Return my_Test
     End Get
     Set(ByVal value As Double)
         if value <> my_Test then RaiseEvent Changed
         my_Test = value
     End Set
 End Property
 Private my_Test As Double = 1.0


C#
public double Test {
	get { return my_Test; }
	set {
		if (value != my_Test)
			if (Changed != null) {
				Changed();
			}
		my_Test = value;
	}
}
private double my_Test = 1.0;
 
Share this answer
 
Comments
[no name] 16-May-17 6:39am    
I suggest to assign the new value before raising the Changed Event ;)
MK-Gii 16-May-17 7:14am    
How do I wait for it from the outside then?
Imagine I have main method where I instantiate the new "A" class. then I call a method in "A" that, after 2 seconds update the property of "A". How in the main method I can wait for that to happen and only then continue the execution?
phil.o 16-May-17 7:37am    
You have to create an event handler for the changed event.
When you instantiate your A object, wire up the Changed event to the event handler. When the event will be raised, your handler will be executed.
MK-Gii 16-May-17 8:19am    
That would still not wait until the DoWork is executing. This is a synchronization task. The stuff that takes most of the time runs on a different thread.
AutoResetEvent and WaitOne seems to be doing what I need. Though not sure how to make it generic for multiple classes as I dont want to copy paste code all over the place.
Ralf Meier 16-May-17 9:26am    
As I said at the beginning : not sure what you tried to achieve ....
If you derive from your base-class the Event is derived too
If you instantiate your base-class (like Phil.o described) you have to catch your Event and raise a new one in this Parent-Class (for example a List(of xyz).

But perhaps you write something more about your issue.
Generally the Task-Approach is (my opinion) not the right way ...

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900