Search code examples
c#dynamicnodesdataflow

Create a function that connects two Dataflow nodes with different types stored in a generic list


I'm trying to make a Node Based editor with C# and Dataflow. I created the following class for the nodes:

public interface INode
{
    public int Id {get; set;}
    public string Name {get; set;}
}

public class Node<TInput, TOutput> : INode
{
    public string Name {get; private set;}
    public int Id {get; set;}
    public IPropagatorBlock<TInput, TOutput> NodeBlock {get; protected set;}

    public Node(string Name, int Id) {
        this.Name = Name;
        this.Id = Id;
    }    
}

Then I have the classes that derive from the class Node:

public class ThresholdNode : Node<int, float>
{
    public ThresholdNode(string Name, int Id) : base(Name, Id)
    {
        NodeBlock = CreateNodeBlock();
    }

    private IPropagatorBlock<int, float> CreateNodeBlock() {
        return new TransformBlock<int, float>(...);
    }
}

And then there's the Node Manager, that's responsible for managing the nodes. The node manager stores all the created nodes inside the following generic list:

public List<INode> NodeList

Since the nodes can have different Input and Output types, I can't put the NodeBlock inside the INode interface. So here's where I'm stuck. I want to make a function inside the NodeManager class that connects two nodes. If I'd know the type of them, I could do the following:

public void ConnectNodes(int originIndex, int destinationIndex) {
    var originNodeBlock = NodeList[originIndex] as IPropagatorBlock<int, float>;
    var destinationNodeBlock = NodeList[destinationIndex] as IPropagatorBlock<float, string>
    originNodeBlock.LinkTo(destinationNodeBlock);
}

But since I don't know what types are their Inputs and Outputs, I can't cast them. I also tried to do it using the dynamic thing, but it threw an exception at runtime, and I believe it had something to do with Dataflow, though I'd like to be proved wrong on that. Here's how I tried to do it using dynamic:

First I created a method on the NodeClass to return the NodeBlock and added the same method to the INode interface:

public interface INode {
    ...
    public dynamic GetNodeBlock();
}

public class Node<TInput, TOutput> : INode {
    ...    
    public dynamic GetNodeBlock() {
        return this.NodeBlock;
    }
}

Then, in the NodeManager class I wrote the following method for connecting the nodes:

public bool ConnectNodes(int originIndex, int destinationIndex) {
    INode originNode = NodeList[originIndex];
    INode destinationNode = NodeList[destinationIndex];
    
    var originNodeBlock = originNode.GetNodeBlock();
    var destinationNodeBlock = originNode.GetNodeBlock();

    originNodeBlock.LinkTo(destinationNodeBlock); // <--- throws exception here
    return true;
}

However, at runtime the LinkTo throws the following exception:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'No overload for method 'LinkTo' takes 1 arguments'


Solution

  • The particular overload of LinkTo you are looking for is implemented as an Extension Method. You can find this detail from the documentation for IPropagatorBlock.

    Unfortunately, the dynamic keyword and Extension method syntax don't work as you would like. IPropagatorBlock does define a LinkTo method with two parameters, but the one you were trying to use that only has one parameter could not be found. Another answer in the link above explains even more about why dynamic and Extension Methods don't play nice.

    As the linked answer says, you can still use Extension methods with dynamic, but you have to call it as a static method and pass in both arguments. In your case, the line with the Exception becomes:

    DataflowBlock.LinkTo(originNodeBlock, destinationNodeBlock); 
    

    (And don't forget using System.Threading.Tasks.Dataflow; if you don't already have it.)

    P.S. Since you said you were a beginner, I suggest avoiding the use of dynamic if you can. See this question for some discussion about why, as well as some design alternatives.