首页 > 解决方案 > Storing a list of different generic types

问题描述

I know this is a frequently discussed topic, since I found a lot of posts talking about it. Unfornately, none of them seems to solve my problem, either because I am not understanding the solution, or I am complicating my problem.

I implemented a simple dataflow, on top of the existent TPL Dataflow library. To make it easier to test, I would like to supply a list of block connections to its constructor, for it to automatically create all blocks and their connections. For that, I created the following class:

internal class DataflowBlockConnection<TInput, TOutput> : IDataflowBlockConnection
{
    public ISourceBlock<TOutput> Source { get; set; }
    public ITargetBlock<TInput> Target { get; set; }
}

Based on this class, I created an empty interface (IDataflowBlockConnection), so that I can have a list of objects that implement it.

public override void CreateStructure()
{
    foreach (IDataflowBlockConnection connection in BlockConnections)
    {
        ConnectBlocks(connection.Source, connection.Target);
    }
}

Unfortanetly, this does not compile, since I don't have information about the Source and Target in the interface, which would allow me to connect the source to the target. That's where other threads diverge from this. With this design I can have a list of different connections with their own input and output types, but I cannot access each block, since I would need information about TInput and TOutput. Am I missing something?

EDIT:

Following @Enigmativity's comment, I have reduced my problem so that it is easier understood.

I have two types of classes, Source Blocks and Target Blocks. They both implement IDataflowBlock interface, since they are both different types of blocks. They both receive a generic type (TInput or TOutput), that can be any type.

Source Blocks can be linked to Target Blocks, but that method is only available for Source Blocks.

Following are the simplified classes (this is a short transcription of TPL Dataflow, just based on my problem):

internal interface IDataflowBlock
{

}

internal interface ISourceBlock<out TOutput> : IDataflowBlock
{
    void LinkTo(ITargetBlock<TOutput> target);
}

internal interface ITargetBlock<in TInput> : IDataflowBlock
{

}

Based on this structure, pipelines can be created, by linking different source blocks to target blocks. My idea, so that I can easily test different pipeline structures, was to create a class that would represent the connection between two blocks, so that I would only need to supply a list of connections, and the blocks would be linked based on the list.

internal interface IDataflowBlockConnection
{

}

internal class DataflowBlockConnection<TInput, TOutput> : IDataflowBlockConnection
{
    public ISourceBlock<TOutput> Source { get; set; }
    public ITargetBlock<TInput> Target { get; set; }
}

Now imagine I wanted to create a list of connections, that have different input and output types. Since the connection class implements an interface, I can do the following:

// Two blocks that act as source, consuming types int and char
        ISourceBlock<int> sourceInt = new SourceBlock<int>();
        ISourceBlock<char> sourceChar = new SourceBlock<char>();

        // One block that acts as target, receiving type string
        ITargetBlock<string> targetString = new TargetBlock<string>();

        // Pipeline structure:
        // sourceInt -> targetString
        // sourceChar -> targetString

        DataflowBlockConnection<string, int> connection1 = new DataflowBlockConnection<string, int>
        {
            Source = sourceInt,
            Target = targetString
        };

        DataflowBlockConnection<string, char> connection2 = new DataflowBlockConnection<string, char>
        {
            Source = sourceChar,
            Target = targetString
        };

        List<IDataflowBlockConnection> blockConnections = new List<IDataflowBlockConnection>
        {
            connection1, connection2
        };

This compiles, and I now have a list of the desired connections to form the pipeline. But I have no way of connecting them, since the LinkTo method belongs to Source Block.

foreach (IDataflowBlockConnection connection in blockConnections)
        {
            // Does not compile, since IDataflowBlockConnection has no information about Source and Target.
            connection.Source.LinkTo(Target);
        }

To be able to access Source and Target from IDataflowBlockConnection, without associating TInput and TOutput to it (which would ruin the possibility of having a list with different TInput and TOutput types), I changed it to this:

internal interface IDataflowBlockConnection
{
    ISourceBlock<TOutput> GetSource<TOutput>();
    ITargetBlock<TInput> GetTarget<TInput>();
}

Now I was thinking about calling these methods with reflection, which (I think, because haven't implemented) would solve my problem. My question is: am I complicating the solution? Is there an elegant solution that I am not seeing?

标签: c#genericstpl-dataflow

解决方案


我偶然发现了动态,正在寻找解决我问题的方法。正如@Theodor Zoulias 所建议的那样,一种解决方案是在运行时使用反射将类型注入方法。另一种解决方案是使用动态,尽管它仍然是一个有争议的话题,因为它取决于情况。这是一个好用的吗?


推荐阅读