TransWikia.com

Can I constrain constructor parameters in C#

Stack Overflow Asked by batekha on December 2, 2020

Say I have

public enum DataFlow{
     Input,
     Output
}

public interface IPort{
  DataFlow Dir{get;}
}


public class Port<T>: IPort
{
  DataFlow Dir{get;}
  T Val;
  public Port(T val, DataFlow dir){
     Val = val;
     Dir = dir;
  }
}
 

public class Link{
       
 public Link(IPort portA, IPort portB){
               
 }
}

In the Link class above, is there a way to constrain the constructor so that it only accepts portA if its Dir property is DataFlow.Output, and portB if its Dir property is DataFlow.Input?

Something like this imaginary made-up syntax…

 
 public Link(IPort portA where IPort.Dir is DataFlow.Output, IPort portB where IPort.Dir is DataFlow.Input){
               
 }

I can do that by checking Dir of each inside at the constructor then throwing if needed, but I was wondering if there was a way to enforce that much like the [NotNull] and where T : c# syntax

2 Answers

You can't constrain the formal parameters like that. What you can do, is to make the Dir property a part of the type system, and let the type checker check it.

Basically, this means that input ports and output ports should be different types. One way to do this is:

public interface IPort {
    // anything else other than Dir, that you want to put in IPort
}

public interface IInputPort : IPort {}

public interface IOutputPort : IPort {}

public class InputPort<T>: IInputPort
{
    T Val;
    public InputPort(T val){
        Val = val;
    }
}

public class OutputPort<T>: IOutputPort
{
    T Val;
    public OutputPort(T val){
        Val = val;
    }
}

The constructor could then be declared as:

public Link(IOutputPort portA, IInputPort portB){

}

Rather than passing in the DataFlow you want as the parameter of the Port constructor, you should just instantiate the desired port class - OutputPort or InputPort.

You can still have your Dir property, but as an extension method:

public static class PortExtensions {
    public static DataFlow Dir(this IPort port) {
        if (port is IInputPort) return DataFlow.Input;
        if (port is IOutputPort) return DataFlow.Output;
        throw new ArgumentException("port must be either input port or output port!", nameof(port));
    }
}

Note that this creates a lot of code duplication if InputPort and OutputPort shares a lot of code, but this can be easily solved by creating an abstract AbstractPort base class that both InputPort and OutputPort inherit from, and putting the common code there.

Correct answer by Sweeper on December 2, 2020

No, because this doesn't make sense. "where" is a design time construct that has the compiler check the type in a generics scenario and you're trying to apply it to a runtime data setting

I suggest you consider using the typing mechanism instead and have an IInputPort and an IOutputPort, and then your code can later check what the passed in implementation is (if for some reason you don't store it as an IInputPort/IOutportPort) to know whether it's input or output (because that's really what you're asking- you want to make sure that something passed to your class is capable of acting as an eg an input). The implementer of your code is free to design one class that implements both and pass an instance of it to each, of course


Alternatively, you check the value passed in and throw a meaningful exception if it's not acceptable. This will hopefully be picked up on as part of the design time process of using your class, by the developer doing the implementation before their class is put into part of a production system.. but it does push the risk that errors in implementation won't be picked up until later down the software development line (possibly after their impl of your code has gone live)

Answered by Caius Jard on December 2, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP