【问题标题】:Pattern matching with generic arguments in C#与 C# 中的泛型参数的模式匹配
【发布时间】:2017-07-26 15:38:08
【问题描述】:

在 C# 中我有这样的情况:

class ModelBase<TKey>
{
    TKey Id { get; set; }
}

class Repo<TModel, TKey> :
    where TModel : ModelBase<TKey>
{
    //code where I access both TModel and TKey types
}

在我的 Repo 类中,我需要同时访问 TModelTKey,但是当我指定 TModel 参数时,模式匹配系统应该能够自动提取 TKey

为了使用TKey 泛型类型,我必须将其声明为泛型参数,因此每次需要创建 Repo 时,我都必须指定特定的模型和相关键类型。我怎样才能消除显式键类型的需要,并通过模式匹配提取它?首选在编译时使用验证的解决方案。

【问题讨论】:

  • 据我所知,你不能。您必须包括所有泛型类型。很可能在您的Repo 中,您将有一个方法来通过它的ID 检索您的ModelBase 对象。在这种情况下,无论如何您都需要TKey 类型作为参数。打字很烦人,如果他们能从第一个泛型参数中推断出来,那就很好,但我们到了。

标签: c# generics pattern-matching


【解决方案1】:

由于两个原因,您的要求在 C# 中是不可能的:

  1. 构造函数中没有类型推断:

    class Foo<T>
    { 
        public Foo(T t) { ... }
    }
    
    var foo = new Foo(1); //compile time error, `int` will not be inferred
    

    这个限制很奇怪,并且与方法类型推断的工作方式完全不一致。构造函数不是方法,但这应该是可能的。

  2. 类型推断要么全部要么没有,没有中间立场:

    T Whatever<T, Q>(Q q) where Q: T { ... }
    

    在任何你可能认为根本行不通的假设语法中进行部分类型推断:

    var blah = NewFoo<Blah>(q); //compile time error
    

    或者

    var blah = NewFoo<Blah,>(q); //compile time error
    

    只会失败。

为什么?好吧,因为语言是按照它的方式设计的。第一个限制可以解决,第二个限制我认为不会很快发生,所以如果您打算等待它,请坐下。

【讨论】:

    【解决方案2】:

    我认为解决此问题的方法可能是声明具有单个属性的非泛型类型来存储主键类型。然后泛型可以继承它并在构造函数中设置属性。

    类似这样的:

    class ModelBase
    {
        public Type KeyType { get; set; }
    }
    
    class ModelBase<TKey> : ModelBase
    {
        public TKey Id { get; set; }
    
        public ModelBase()
        {
            KeyType = typeof(TKey);
        }
    }
    
    class Repo<TModel> where TModel : ModelBase, new()
    {
        // code where I access both TModel and TKey types
        public void Test()
        {
            var modelType = typeof(TModel).Name;
            var keyType = new TModel().KeyType.Name;
            Console.WriteLine($"{modelType}     {keyType}");
        }
    }
    

    将允许您编写这样的代码:

    class MyIntEntity : ModelBase<int>
    {
        public new int Id { get; set; }
    }
    
    class MyStringEntity : ModelBase<string>
    {
        public new string Id { get; set; }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            var repoIntegerKey = new Repo<MyIntEntity>();
            var repoStringKey = new Repo<MyStringEntity>();
    
            repoIntegerKey.Test(); // prints "MyIntEntity        Int32"
            repoStringKey.Test();  // prints "MyStringEntity     String"
            Console.ReadLine();
        }        
    }
    

    【讨论】:

    • 这仅使您能够访问代表泛型类型参数TKeyType实例。这与访问泛型类型参数本身非常不同。如果知道类型就足够了,那么这是可行的。
    • 这只适用于极端情况。对于绝大多数情况,您需要说明类型以便您可以利用该属性。
    • 此外,这不会按原样编译,并且在声明子类型时必须在 Id 属性上使用 new 关键字。这种对子类属性的“覆盖”应该有助于指出您所做的是一种反模式。
    猜你喜欢
    • 1970-01-01
    • 2017-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-30
    • 2011-10-25
    • 2014-07-01
    • 1970-01-01
    相关资源
    最近更新 更多