【问题标题】:Non-constructed generics as properties (eg. List<T>)非构造泛型作为属性(例如 List<T>)
【发布时间】:2011-03-05 11:57:23
【问题描述】:

问题

这是我不久前遇到的,并且能够以某种方式解决它。但现在它又回来了,满足了我的好奇心——我很想得到一个明确的答案。

基本上,我有一个通用的 dgv BaseGridView&lt;T&gt; : DataGridView where T : class。基于BaseGridView 构造的类型(如InvoiceGridView : BaseGridView&lt;Invoice&gt;)稍后在应用程序中使用,以使用BaseGridView 提供的共享功能(如虚拟模式、按钮等)显示不同的业务对象。

现在有必要创建一个引用这些构造类型的用户控件,以控制来自BaseGridView 的一些共享 功能(例如过滤)。因此,我希望在用户控件上创建一个公共属性,使我能够将其附加到 Designer/code 中的任何 BaseGridViewpublic BaseGridView&lt;T&gt; MyGridView { get; set; }。问题是,它不起作用:-) 编译时,我收到以下消息:

找不到类型或命名空间名称“T”(您是否缺少 using 指令或程序集引用?)

解决方案?

我意识到我可以将共享功能提取到一个接口,将BaseGridView 标记为实现该接口,然后在我的 uesr 控件中引用创建的接口。

但我很好奇是否存在一些神秘的 C# 命令/语法可以帮助我实现我想要的 - 而不会用我并不真正需要的接口污染我的解决方案:-)

编辑:作为参考,我确实尝试了这种无辜的解决方法:BaseGridView&lt;object&gt; MyGridView { get; set; },并且...仍然不是答案:无法将类型“InvoiceGridView”隐式转换为“ BaseGridView'.

部分成功(编辑 2)

好的,因为只在接口上支持协方差,所以我承认失败并定义了一个接口(只显示了其中的一部分):

public interface IBaseGridView<out T> where T : class
{
    bool ScrollTo(Predicate<T> criteria);
    bool ScrollTo(T object);
}

我现在可以将我心爱的 InvoiceGridView 投给 IBaseGridView&lt;object&gt; - 太棒了,我又是一个快乐的男孩了 :-) 然而,第二个 ScrollTo 正在给予我在编译时遇到了麻烦:

无效方差:类型参数“T”必须在“GeParts.Controls.IBaseGridView.ScrollTo(T)”上逆变有效。 'T' 是协变的。

我现在不得不将签名修改为ScrollTo(object o) - 这并不理想,但可以完成工作。令我吃惊的是编译器抱怨第二个ScrollTo 但对第一个很满意。所以似乎不允许传递out T实例,但是使用类型本身(例如在Predicate&lt;T&gt; 中)可以吗?好像比较挑剔……

【问题讨论】:

  • 你不能把这个共享代码放在所有这些网格的公共基类中吗?
  • 在大多数情况下 - 不,因为大多数共享功能需要使用 T - 排序、滚动、过滤。
  • 请不要在标题中重复“C#”之类的标签。这就是标签的用途。

标签: c# .net generics interface properties


【解决方案1】:

自从你写了

但我很好奇是否存在一些神秘的 C# 命令/语法可以帮助我实现我想要的目标

我想补充一点,C# 4.0 可以使用 来将派生类型替换为基类型作为协方差。所以你可以这样做

public BaseGridView对象> MyGridView { get;放; }

所以你得到了一个众所周知的类型,但你可以返回任何你想要的 BaseGridView。不幸的是,唯一的问题是只允许在接口上使用协方差! :(

【讨论】:

  • 协方差确实是我一直在寻找的!不久前阅读它,我知道这些方面有一些东西 - 因此我提出了问题。不幸的是,它现在只是接口,所以所有的道路似乎都通向相同的结局:-(哦,你在哪里,C# 5?
  • " 但是使用类型本身(例如,在 Predicate 中)可以吗?” - 它允许您这样做的原因是 Predicate 本身在 T 中是逆变的。
  • 啊,我明白了……现在一切都说得通了,感谢分享见解。
【解决方案2】:

不应该是这样的吗:

public BaseGridView MyGridView { get;放; }

public BaseGridView<T> GetMyGridView<T> { return whatever; }
public void SetMyGridView<T>( BaseGridView<T> bgv) { whatever = bgv; }

??

已编辑。马修是对的,属性可能不是通用的。您必须使用 getter/setter。

【讨论】:

    【解决方案3】:

    据我所知,C# 不支持泛型属性。您的选择是创建泛型方法或将泛型类型作为类定义的一部分。

    例如:

    public BaseGridView<T> GetMyGridView<T>() { ... }
    public void SetMyGridView<T>(T gridView) { ... }
    

    class MyClass<T> {
        public BaseGridView<T> MyGridView { get; set; }
    }
    

    【讨论】:

    • 选项1:泛型方法很好,但问题是:如何声明支持字段?选项 2:是的,但是我需要从 MyClass 用户控件为我们拥有的每个业务对象创建构造类型。有比这更好的解决方法:-)
    • 在 C# 4.0 中,我相信您可以使用 BaseGridView 作为您的支持字段(尽管在以前的版本中您当然不能这样做)。您的另一个选择是 BaseGridView 的祖先。无论如何,我认为您的问题的答案是(1)没有语法可以满足您的需求,并且(2)您应该重新审视您的信念,即您不需要通用接口。
    • 你说得对,C# 4.0(我们正在使用的版本)有一个几乎可以解决问题的特性——协方差。但目前可悲的是它只是接口:-(所以可能是 (1) == true && 1 -> 2...
    【解决方案4】:

    我刚刚尝试将一些代码拼凑在一起,对我来说效果很好:

        public class A<T> where T : class
        {
            public virtual A<T> ARef
            {
                get { return default(A<T>); }
            }
        }
    
        public class B : A<B>
        {
            public override A<B> ARef
            {
                get
                {
                    return base.ARef;
                }
            }
        }
    

    【讨论】:

    • 确实,不一样 :-) 您现在应该创建不从 A 或 B 继承的 C,具有未构造类型 A 的属性/字段,并尝试访问A的一些方法。
    【解决方案5】:

    以下可能会起作用:

    public BaseGridView<T> MyGridView<T> { get; set; }
    

    您原来答案的问题是类型参数必须出现在方法或类声明中,而不仅仅是返回值。

    请注意,编译器无法从返回值推断泛型类型,因此您需要在每次调用 MyGridView 时指定 T

    【讨论】:

    • Matthew 是对的,这行不通。确实,如果我将 T 添加到类定义中,它会 - 但随后必须从这个通用用户控件为我们拥有的每个业务对象创建构造类型。有比这更好的解决方法:-)
    猜你喜欢
    • 1970-01-01
    • 2019-02-13
    • 1970-01-01
    • 2017-12-31
    • 1970-01-01
    • 1970-01-01
    • 2016-03-19
    • 2022-01-01
    • 1970-01-01
    相关资源
    最近更新 更多