【问题标题】:C# Passing current generic class instance to other classC#将当前泛型类实例传递给其他类
【发布时间】:2017-11-27 23:22:48
【问题描述】:

最近我开始学习泛型。我在存储对泛型类实例的引用时遇到了麻烦。如您所见,我的 ListHandler 类可以存储对特定类型 BaseClass 的引用。我很想自己注册 BaseClass 实例,这就是为什么我想通过添加“where”来保证他们将使用 BaseParamClass。无论如何 - 它不会编译。'This',即使在类中使用'where'关键字,也不知道 T 实际上是 BaseClassParam。我不知道这里出了什么问题,我在任何地方都找不到答案。我将不胜感激提示/指南/解决方案。

public class ListHandler
{
    private List<BaseClass<BaseParamClass>> list;

    public ListHandler()
    {
        list = new List<BaseClass<BaseParamClass>>();
    }

    public void Register(BaseClass<BaseParamClass> param)
    {
        list.Add(param);
    }
}

public class BaseClass<T> where T : BaseParamClass
{
    private ListHandler listHandler;

    public T Param { get; private set; }

    public BaseClass(ListHandler listHandler)
    {
        this.listHandler = listHandler;
        listHandler.Register(this); //throws error
    }
}

【问题讨论】:

  • Afaic 那是因为协方差只允许用于接口,检查这里:stackoverflow.com/questions/6508529/…
  • 您还有一个与封闭类同名的方法public void BaseClass。这也是不允许的。那是构造函数吗?
  • 您遇到的错误是什么?也没有人可以编译你的代码,因为没有人有BaseParamClass,因为你没有在你的问题中包含这个。
  • 除了其他 cmets,我不会使用 List&lt;BaseClass&lt;BaseParamClass&gt;&gt; 调用世界上最简单的数据结构。我很想知道更多关于这段代码的高级用途,因为可能有另一种方法 - 即可能是封装和接口。
  • 为什么BaseClass&lt;T&gt; 包含ListHandler,而其中包含BaseClass&lt;&gt; 的列表?似乎相当递归没有一点,除非我错过了?

标签: c# generics collections


【解决方案1】:

我有另一个选择。

让我们将BaseClass&lt;T&gt; 类拆分为具有非泛型基类的两个类,如下所示:

public class BaseClass
{
    protected ListHandler listHandler;

    public BaseClass(ListHandler listHandler)
    {
        this.listHandler = listHandler;
    }
}

public class BaseClass<T> : BaseClass where T : BaseParamClass
{

    public T Param { get; private set; }

    public BaseClass(ListHandler listHandler)
        : base(listHandler)
    {
        listHandler.Register(this); // Compiles nicely! Yay!
    }
}

现在,ListHandler 中的list 可以定义为private List&lt;BaseClass&gt; list;。这意味着将任何BaseClass 项目添加到列表中没有问题。然后,我们还可以定义两种方法来从ListHandler 注册和获取BaseClass&lt;T&gt; 的通用版本。它看起来像这样:

public class ListHandler
{
    private List<BaseClass> list;

    public ListHandler()
    {
        list = new List<BaseClass>();
    }

    public void Register<T>(BaseClass<T> param) where T : BaseParamClass
    {
        list.Add(param);
    }

    public BaseClass<T> Fetch<T>() where T : BaseParamClass
    {
        return list.Select(x => x as BaseClass<T>).Where(x => x != null).FirstOrDefault();
    }
}

所以,给定一个 public class FooParam : BaseParamClass { } 类,我可以编写以下代码:

ListHandler listHandler = new ListHandler();
BaseClass<FooParam> baseClass = new BaseClass<FooParam>(listHandler);
BaseClass<FooParam> baseClass2 = listHandler.Fetch<FooParam>();

Console.WriteLine(object.ReferenceEquals(baseClass, baseClass2));

这段代码的结果是True被写入控制台——这意味着我可以成功地从ListHandler获取BaseClass&lt;FooParam&gt;的实例。

【讨论】:

  • @MateuszJaworski - 我想我仍然错过了你的观点。你能解释一下你实际上想要做什么吗? (不要解释这些类应该做什么,解释其根本目的是什么。)
  • 我想管理打开的弹出窗口,打开后添加到堆栈中。每个弹出窗口都在管理器中注册。我需要 BasePopup 的通用类,它将具有通用 ViewModel 并且能够在弹出窗口管理器中注册自己。
  • @MateuszJaworski - 注册弹出窗口有什么作用?
  • 能够在动画显示/隐藏完成时订阅事件,能够恢复活动弹出窗口的堆栈等等
【解决方案2】:

为什么您的代码无法编译

为了完全理解您的代码无法编译的原因,您必须深入了解covariance and contravariance,这是一个很大的话题,很难在 SO 答案中解释。如果您已经达到继承多态性是您的第二天性的地步,那可能会特别令人困惑。规则的不同足以让你头疼。

这是令人困惑的——

你已经习惯这样做了:

object a = new String(...);

但是泛型不允许你这样做!

List<object> c = new List<string>();  //Compiler error

这是因为这两个列表的关联方式与 objectstring 的关联方式不同。一个不继承另一个。相反,它们是泛型类型定义的不同变体。在通用世界中,您不能将一个分配给另一个。这个也是一样的:

void Foo<T>() where T: BaseParamClass 
{
    BaseClass<BaseParamClass> a = new BaseClass<T>(); //Compiler error
}

在此示例中,T 可以是 BaseParamClass 或其派生类型之一。它们不是同一类型。所以为了保持类型安全,编译器必须禁止这个赋值,以及你的 Register 调用,它具有相同的类型不匹配。

解决这个问题的标准方法

您需要一个协变接口。这些允许从派生分配到基础。例如,虽然这仍然是非法的:

List<object> a = new List<string>();  //Compiler error

这完全没问题:

IEnumerable<object> e = new List<string>();  //Is OK

因为IEnumerable 被声明为协变的,像这样:

interface IEnumerable<out T> 

这意味着它可以以这种方式分配。它之所以有效,是因为使用out 还向接口添加了编译器约束:它可以用来检索东西...

interface IEnumerable<out T> 
{
    T Item[int index];
}

...但它不能接受任何东西:

interface IEnumerable<out T> 
{
    Add(T item); //Compiler error
}

这些约束允许泛型提供早期绑定类型安全,同时仍然允许某些形式的(非继承)多态性。

我的建议

根据您的评论,听起来您只需要一个容器(显然是一个堆栈)来保存对这些 BaseClass&lt;T&gt; 实例的引用。如果您遵循关注点分离,堆栈实际上不需要对T 做任何事情,除了存储和检索它,并允许它自己注册。

既然这是一个单独的关注点,请创建一个单独的接口。

为了简单起见,可能要避免完全使用泛型。

一种方法--

创建一个接口,允许访问堆栈需要知道的关于它所包含的项目的所有信息。例如,如果堆栈包含各种弹出窗口,您可能希望公开弹出窗口的标题。

interface IStackable
{
    string Title { get; set; }
}

现在像这样使用它:

public class ListHandler 
{
    private readonly Dictionary<string, IStackable> list;

    public ListHandler()
    {
        list = new Dictionary<string, IStackable>();
    }

    public void Register(IStackable item)
    {
        list.Add(item.Title, item);
    }
}

public class BaseClass<T> : IStackable where T : BaseParamClass
{
    private ListHandler listHandler;

    public T Param { get; private set; }

    public BaseClass(ListHandler listHandler)
    {
        this.listHandler = listHandler;
        listHandler.Register(this);
    }

    public string Title { get; set; }
}

除非有其他要求,否则您不需要让它变得比这更复杂。

【讨论】:

  • 感谢您的建议,我想我太想坚持我的解决方案了。毕竟我稍微改变了架构,现在它似乎工作了。无论如何......我还有很长的路要走,需要学习有关协变和逆变的东西。感谢您的帮助;)
【解决方案3】:

为什么不将ListHandler 也设为通用?

public class ListHandler<T>
{
    private List<BaseClass<T>> list;

    public ListHandler()
    {
        list = new List<BaseClass<T>>();
    }

    public void Register(BaseClass<T> param)
    {
        list.Add(param);
    }
}

public class BaseClass<T> 
{
    private ListHandler<T> listHandler;

    public T Param { get; private set; }

    public BaseClass(ListHandler<T> listHandler)
    {
        this.listHandler = listHandler;
        listHandler.Register(this); 
    }
}

另外,让BaseClass&lt;T&gt; 包含对一个引用了BaseClass&lt;T&gt; 本身的类的引用对我来说似乎很奇怪。

【讨论】:

  • 制作通用 ListHandler 是我首先想到的,但它没有帮助或者可能我做错了什么。
【解决方案4】:

您真正需要做的就是添加一个界面。这有效:

public class BaseParamClass
{
}

public class ListHandler 
{
    private List<IBase<BaseParamClass>> list;

    public ListHandler()
    {
        list = new List<IBase<BaseParamClass>>();
    }

    public void Register(IBase<BaseParamClass> param)
    {
        list.Add(param);
    }
}

public interface IBase<T> where T : BaseParamClass
{
    T Param {get; }
}

public class BaseClass : IBase<BaseParamClass>
{
    private ListHandler listHandler;

    public BaseParamClass Param { get; private set; }

    public BaseClass(ListHandler listHandler)
    {
        this.listHandler = listHandler;
        listHandler.Register(this); 
    }
}

Working code on DotNetFiddle

【讨论】:

  • 是的......在阅读了一些我认为使用界面的东西之后,它似乎可以工作,但我想知道是否还有其他方法。我也不确定如果我制作例如 OtherClass : BaseClass 并且我希望 OtherClass 有 OtherClassParam Param 是否会起作用
  • 即使我们可以让它发挥作用,您也不应该与技术抗争。你能解释一下这些阶级关系的基本原理吗?也许有更简单的方法。
  • 我想管理打开的弹出窗口,打开后添加到堆栈中。每个弹出窗口都在管理器中注册。我需要 BasePopup 的通用类,它将具有通用 ViewModel 并且能够在弹出窗口管理器中注册自己。我只是无法将它们添加到堆栈中,这是我唯一的问题。
  • @Mateusz 为你添加了另一个答案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-23
  • 1970-01-01
  • 1970-01-01
  • 2021-12-04
  • 1970-01-01
  • 2016-02-12
相关资源
最近更新 更多