MK-Gii Ответов: 2

Универсальный метод для нескольких классов


Эй,

У меня есть много классов, которые имеют опору и нуждаются в каком-то механизме, чтобы ждать изменения свойства.
Представьте себе это:

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 */ }
}


Теперь мне нужно будет создать больше таких классов в будущем, и мне нужен какой-то общий метод/расширение, чтобы дождаться изменения свойства "данные".
Я знаю, что могу сделать это с помощью событий, а затем с помощью потока.Сон, но это не лучший способ кодирования, так как сон занимает поток.
Другой способ состоял в том, чтобы использовать TaskCompletionSource следующим образом:

// 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;
        }
    }


Вышесказанное позволяет иметь это в основном методе:

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

}


..Но тогда мне пришлось бы писать расширение для каждого нового класса, который я создаю, а это не то, что я хочу. Кто-нибудь знает способ, как это обобщить?

Спасибо!

Что я уже пробовал:

Проверьте код выше - попробовал успокоить несколько вещей здесь.

2 Ответов

Рейтинг:
19

Richard Deeming

Три очевидных варианта:

1) производите от базового класса, который определяет событие, и объявите свой метод расширения, чтобы взять экземпляр этого базового класса.

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) реализуйте интерфейс, который определяет событие, и объявите свой метод расширения, чтобы взять экземпляр этого интерфейса:
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) передайте методы для присоединения и отсоединения обработчика событий в виде действий:
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;
}


Ralf Meier

@Ричард:
потому что я не так хорошо знаком с C# : что значит "изменился?" - здесь я имею в виду вопросительный знак ...

Richard Deeming

Это "нулевой условный оператор", также известный как "нулевой оператор распространения", который был введен в C# 6 (Visual Studio 2015).

C#: новый и улучшенный C# 6.0[^]
Чистый вызов обработчика событий с помощью C# 6[^]

MK-Gii

Спасибо. Хорошее объяснение и прекрасное решение.

Рейтинг:
0

Ralf Meier

Я не уверен, чего вы пытаетесь достичь ...
1-й:
если у вас есть вещи, которые являются общими для многих классов, вы можете закодировать их в один базовый класс и позволить всем другим классам быть производными от него
2-й:
вам не нужно кодировать задачу, чтобы проверить, изменяется ли свойство вашего класса. Это очень легко сделать внутри Setter (- Method) вашего свойства - здесь вы можете вызвать событие

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


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;


0x01AA

Я предлагаю присвоить новое значение перед тем, как поднимать измененное событие ;)

MK-Gii

Как же мне тогда ждать его снаружи?
Представьте себе, что у меня есть основной метод, в котором я создаю экземпляр нового класса "А". затем я вызываю метод В "А", который через 2 секунды обновляет свойство"а". Как в основном методе я могу ждать, пока это произойдет,и только тогда продолжать выполнение?

phil.o

Вы должны создать обработчик событий для измененного события.
Когда вы создаете экземпляр объекта A, подключите измененное событие к обработчику событий. Когда событие будет вызвано, ваш обработчик будет выполнен.

MK-Gii

Вот бы еще не подождать, пока метода dowork выполняется. Это задача синхронизации. То, что занимает большую часть времени, работает в другом потоке.
AutoResetEvent и WaitOne, похоже, делают то, что мне нужно. Хотя я не знаю, как сделать его универсальным для нескольких классов, так как я не хочу копировать код вставки повсюду.

Ralf Meier

Как я уже сказал в самом начале: не уверен, чего вы пытались достичь ....
Если вы производны от своего базового класса, то событие тоже является производным
Если вы создаете экземпляр своего базового класса (как описано в Phil.o), вы должны поймать свое событие и создать новое в этом родительском классе (например, список(xyz).

Но, возможно, вы напишете что-то еще о своей проблеме.
Вообще подход к задаче (на мой взгляд )не совсем правильный...