【问题标题】:Using Struct as Generic Parameter and casting to the base interface cause the error: must be a non-nullable value type使用 Struct 作为通用参数并转换为基接口会导致错误:必须是不可为空的值类型
【发布时间】:2020-03-17 20:55:41
【问题描述】:

我需要创建一个 ConcreteProviderX 实例的集合以在其上执行 LINQ 运算符。我更喜欢将 struct 用于 ConcreteArgumentsX 类型,但在创建数组时强制转换为基本接口 IArguments 会导致编译时错误:类型“IArguments”必须是不可为空的值类型才能将其用作参数“T”泛型类型或方法“ICommandProvider”(代码 = CS0453)。对于 ConcreteArgumentsX 类型,我有一个使用类而不是结构的替代实现,但它会导致运行时 System.InvalidCastException(无法将“SetProvider”转换为“ICommandProvider”)。对于 struct 和 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()
            };
        }
    }
}

【问题讨论】:

  • 你有一个不变的泛型声明,因此你得到一个错误。 out 参数不允许您有变体声明。你应该使用精确的类型声明,比如ICommandProvider&lt;ConcreteArguments1&gt; provider = new ConcreteProvider1();
  • 请查看此线程以获取解释和可能的解决方法Why do C# out generic type parameters violate covariance?
  • 请看我更新的答案。我添加了代码进行测试。欢迎提出任何问题
  • @PavelAnikhouski 感谢您的评论。请你展示一下代码应该是这样的吗?这对我们真的很有帮助。

标签: c# generics struct


【解决方案1】:

struct 是值类型,IArguments 是引用类型。引用类型是NULLable,但是值类型是not NULLable。 由于通用约束始终是“AND”,这意味着您的 T 应该满足写在您的 where 语句 - where T : struct, IArguments 中的所有条件。 所以这就是你出错的原因:

类型“IArguments”必须是不可为空的值类型,才能在泛型类型或方法“ICommandProvider”中用作参数“T”

更新:

也许这个实现会有用:

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

其他代码如下所示:

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 感谢您的错误解释和建议的答案。但我需要保存一个特定的 T 约束,例如。对于 ConcreteProvider1 它只需要是 ConcreteArguments1 (或实现一些附加接口的类型,例如 IConcreteArguments1 )
  • @E.Makarchuk 那么你的泛型将不起作用,因为结构 ConcreteArguments1 不等于 ConcreteArguments2
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-14
  • 2022-06-13
  • 2014-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多