1

I need to create a collection of ConcreteProviderX instances to execute LINQ operators on it. I prefer to use struct for ConcreteArgumentsX types, but casting to base interface IArguments while creating an array causes the compile-time error: The type 'IArguments' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'ICommandProvider' (Code = CS0453). I have an alternative implementation with class instead of struct for ConcreteArgumentsX types, but it causes a run-time System.InvalidCastException (Unable to cast "SetProvider" to "ICommandProvider"). How the error would be solving for the case with struct and for the case with class?

namespace ErrorUsingStructForConcreteArgumentsX
{
    interface ICommandProvider<T> where T : struct, IArguments
    {
        void F(string command, out T? arguments);
    }

    class ConcreteProvider1 : ICommandProvider<ConcreteArguments1>
    {
        public void F(string command, out ConcreteArguments1? arguments) { arguments = null; /* other code */ }
    }

    class ConcreteProvider2 : ICommandProvider<ConcreteArguments2>
    {
        public void F(string command, out ConcreteArguments2? arguments) { arguments = null; /* other code */ }
    }

    interface IArguments { }
    struct ConcreteArguments1 : IArguments { /* some value type properties */ }
    struct ConcreteArguments2 : IArguments { /* some value type properties */ }

    class Program
    {
        static void Main(string[] args)
        {
            ICommandProvider<ConcreteArguments1> provider = new ConcreteProvider1(); // ok

            // compile-time error
            var providers = new ICommandProvider<IArguments>[]
            {
                (ICommandProvider<IArguments>)new ConcreteProvider1(),
                (ICommandProvider<IArguments>)new ConcreteProvider2()
            };
        }
    }
}

namespace AlternativeUsingClassForConcreteArgumentsX
{
    interface ICommandProvider<IArguments>
    {
        void F(string command, out IArguments arguments);
    }

    class ConcreteProvider1 : ICommandProvider<ConcreteArguments1>
    {
        public void F(string command, out ConcreteArguments1 arguments) { arguments = null; /* other code */ }
    }

    class ConcreteProvider2 : ICommandProvider<ConcreteArguments2>
    {
        public void F(string command, out ConcreteArguments2 arguments) { arguments = null; /* other code */ }
    }

    interface IArguments { }
    class ConcreteArguments1 : IArguments { /* some value type properties */  }
    class ConcreteArguments2 : IArguments { /* some value type properties */ }

    class Program
    {
        static void Main(string[] args)
        {
            ICommandProvider<ConcreteArguments1> provider = new ConcreteProvider1(); // ok

            // runtime error
            var providers = new ICommandProvider<IArguments>[]
            {
                (ICommandProvider<IArguments>)new ConcreteProvider1(),
                (ICommandProvider<IArguments>)new ConcreteProvider2()
            };
        }
    }
}
  • You have an invariant generic declaration, therefore you a getting an errors. `out` parameters doesn't allow you to have a variant declarations. And you should use an exact type declaration, like this `ICommandProvider provider = new ConcreteProvider1();` – Pavel Anikhouski Mar 18 '20 at 10:46
  • Please, have a look at this thread for the explanation and possible workarounds [Why do C# out generic type parameters violate covariance?](https://stackoverflow.com/questions/8913814/why-do-c-sharp-out-generic-type-parameters-violate-covariance) – Pavel Anikhouski Mar 18 '20 at 10:46
  • please, see my updated answer. I've added code to test. Feel free to ask any question – StepUp Mar 18 '20 at 12:02
  • 1
    @PavelAnikhouski thanks for your comment. Could you, please, show how code should look like this? It would be really helpful to us. – StepUp Mar 18 '20 at 12:07

1 Answers1

2

struct is value type and IArguments is reference type. Reference type is NULLable, however value type is not NULLable. As generic constraints are always "AND", it means that your T should satisfy all conditions which written in your where statement - where T : struct, IArguments. So this is a reason why you've got an error:

The type 'IArguments' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'ICommandProvider'

UPDATE:

Maybe this implementation will be useful:

static void Main(string[] args)
{
    ICommandProvider<IArguments> provider = new ConcreteProvider1();
    var providers = new ICommandProvider<IArguments>[]
    {
        new ConcreteProvider1(),
        new ConcreteProvider2()
    };
}

And other code looks like this:

interface ICommandProvider<T> where T : IArguments
{
    void F(string command, T arguments);
}

class ConcreteProvider1 : ICommandProvider<IArguments>
{
    public void F(string command, IArguments arguments)
    {
        throw new NotImplementedException();
    }
}

class ConcreteProvider2 : ICommandProvider<IArguments>
{
    public void F(string command, IArguments arguments)
    {
        throw new NotImplementedException();
    }
}

interface IArguments { }
class ConcreteArguments1 : IArguments { /* some value type properties */ }
class ConcreteArguments2 : IArguments { /* some value type properties */ }
StepUp
  • 36,391
  • 15
  • 88
  • 148
  • StepUp Thanks for the error explanation and suggested answer. But I need to save a specific T constraint, ex. for ConcreteProvider**1** it only needs to be ConcreteArguments**1** (or type that implements some additional interface e.g. **I**ConcreteArguments**1**) – E.Makarchuk Mar 23 '20 at 12:47
  • @E.Makarchuk then your generic will not work as struct `ConcreteArguments1` is not equal to `ConcreteArguments2` – StepUp Mar 23 '20 at 13:58