简体   繁体   中英

How to register an event call a delegated method in a different class using an extension method in .NET Core?

I'm trying to achieve async data movement between instances of different classes without actually using class references and building an extension method for their base classes instead.

It's a .NET Core Class Library, targeting .NET Standard 1.6.

Let's say I have a class with an async void method that continuously updates a property of the same class:

public abstract class DataRetriever : DataRetrieverAbstract
{
    public int CollectionInterval { get; private set; }

    public float DataResult { get; private set; }

    private float ReadData()
    {
        return 1; //in reality it returns different values every time
    }

    public async void StartReading(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            await Task.Delay(CollectionInterval * 1000);
            DataResult = ReadData();
        }
    }
}

I also have a second class with an async void method which writes the data somewhere:

public abstract class DataWriter : DataWriterAbstract
{
    public async void WriteData(float dataToWrite)
    {
        // some writing magic
    }
}

How can I build an extension method that basically is going to "pair" two or more instances of these classes together and delegate the events?

Something like this:

public static DataRetrieverAbstract PairToWriter (this DataRetrieverAbstract retriever, DataWriterAbstract writer)
{
    // ???
}

To then use it like this:

var dataRetriever1 = new DataRetriever();
var dataRetriever2 = new DataRetriever();
var dataRetriever3 = new DataRetriever();

var dataWriter1 = new DataWriter();
var dataWriter2 = new DataWriter();

dataRetriever1.PairToWriter(dataWriter1).PairToWriter(dataWriter2);
dataRetriever3.PairToWriter(dataWriter2);

// ... stuff goes on
dataRetriever1.StartReading(token);
dataRetriever2.StartReading(token);
dataRetriever3.StartReading(token);

So basically we have one Retriever writing into two different instances of DataWriter and 3-rd Retriever using just the second instance.

What is the best way to do it?

The async void stuff is a bit of a red herring, but you could achieve this using an event with multiple subscribers. Internally, this is similar to storing instances of the reader in a list inside the writer. Here's an example:

class Program
{
    class DataRetriever
    {
        public event Action<float> DataReady;

        private float ReadData() => 1;

        public async Task StartReading()
        {
            while (true)
            {
                await Task.Delay(1000);
                DataReady?.Invoke(ReadData());
            }
        }
    }

    class DataWriter
    {
        public void WriteData(float dataToWrite)
        {
            Console.WriteLine(dataToWrite);
        }
    }

    static void Main(string[] args)
    {
        var reader1 = new DataRetriever();
        var reader2 = new DataRetriever();
        var reader3 = new DataRetriever();
        var writer1 = new DataWriter();
        var writer2 = new DataWriter();

        reader1.DataReady += writer1.WriteData;
        reader2.DataReady += writer2.WriteData;
        reader3.DataReady += writer2.WriteData;

        Task.Run(reader1.StartReading);
        Task.Run(reader2.StartReading);
        Task.Run(reader3.StartReading);

        Console.ReadKey();
    }
}

You just need to add some event to base abstract class and subscribe to it. By the way, I suggest to use Task instead of void, because your read/write method can be non completed when your app is finished, so you doesn't "see" the result of these methods:

Here it's DataRetrieverAbstract and his derrived class:

    public abstract class DataRetrieverAbstract
    {
        public virtual event Action<float> DataReaded;

        protected void FireDataReaded(float arg)
        {
            DataReaded?.Invoke(arg);
        }
    }

    public class DataRetriever : DataRetrieverAbstract
    {
        public int CollectionInterval { get; set; }

        public float DataResult { get; private set; }

        private float ReadData()
        {
            return 1; //in reality it returns different values every time
        }

        public async Task StartReading(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                await Task.Delay(CollectionInterval * 1000);
                DataResult = ReadData();
            }
            FireDataReaded(DataResult);
        }
    }

Next you should move WriteData to base class if you want to subscribe on event using base abstract class not derived:

    public abstract class DataWriterAbstract
    {
        public abstract void WriteData(float dataToWrite);
    }

    public class DataWriter : DataWriterAbstract
    {
        public override void WriteData(float dataToWrite)
        {
            // some writing magic
            Console.WriteLine(dataToWrite);
        }
    }

So your extension is very simple:

    public static DataRetrieverAbstract SubscribeOnReaded(this DataRetrieverAbstract retriever, DataWriterAbstract writer)
    {
        retriever.DataReaded += writer.WriteData;
        return retriever;
    }

And usage:

    var dataRetriever1 = new DataRetriever() { CollectionInterval = 2 };
    var dataRetriever2 = new DataRetriever() { CollectionInterval = 3 };
    var dataRetriever3 = new DataRetriever() { CollectionInterval = 4 };

    var dataWriter1 = new DataWriter();
    var dataWriter2 = new DataWriter();

    dataRetriever1.SubscribeOnReaded(dataWriter1).SubscribeOnReaded(dataWriter2);
    dataRetriever3.SubscribeOnReaded(dataWriter2);

    //...
    CancellationTokenSource source = new CancellationTokenSource();
    var tasks = new[] { dataRetriever1.StartReading(source.Token), dataRetriever2.StartReading(source.Token), dataRetriever3.StartReading(source.Token) };

    source.Cancel();
    // If you want to wait a tasks results – uncomment the line below
    //Task.WaitAll(tasks);

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