简体   繁体   English

C#将当前泛型类实例传递给其他类

[英]C# Passing current generic class instance to other class

lately I started to learn generics. 最近我开始学习泛型。 I run into trouble with storing references to generic classes instances. 我遇到了存储对泛型类实例的引用的麻烦。 As you can see, my class ListHandler can store references to specific type of BaseClass. 如您所见,我的类ListHandler可以存储对特定类型的BaseClass的引用。 I would love to register BaseClass instances by themselves, which is why I wanted to guarantee that they will use BaseParamClass by adding 'where'. 我很乐意自己注册BaseClass实例,这就是为什么我要保证他们将通过添加'where'来使用BaseParamClass。 Anyway - it does not compile.'This', does not know that T is actually BaseClassParam even with 'where' keyword in class. 无论如何 - 它不会编译。'这个',即使在课堂上使用'where'关键字,也不知道T实际上是BaseClassParam。 I don't know what is wrong here and I couldn't find answer anywhere. 我不知道这里有什么问题,我无法在任何地方找到答案。 I would be grateful for tips/guides/solutions. 我将非常感谢提示/指南/解决方案。

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
    }
}

Why don't you make ListHandler generic as well? 为什么不使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); 
    }
}

Also, it seems strange to me to have BaseClass<T> contain a reference to a class that has a reference to BaseClass<T> itself. 另外,让BaseClass<T>包含对引用BaseClass<T>本身的类的引用似乎很奇怪。

Why your code doesn't compile 为什么你的代码不能编译

In order to fully understand why your code doesn't compile, you'll have to dive into covariance and contravariance , which is a big topic and hard to explain in an SO answer. 为了完全理解为什么你的代码不能编译,你将不得不深入研究协方差和逆变 ,这是一个很大的主题,很难在SO答案中解释。 It can be especially confusing if you've gotten to a point where inheritance polymorphism is second nature to you; 如果你已经达到了继承多态性对你来说是第二天性的那一点,那会特别令人困惑; the rules are just different enough to be make your head hurt. 规则只是不同,足以让你头疼。

Here is what is confusing-- 这是令人困惑的 -

You're used to doing this: 你习惯这样做:

object a = new String(...);

But generics don't let you do this! 但是仿制药不允许你这样做!

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

That's because those two Lists are not related the same way that object and string are related. 那是因为这两个列表的关联方式与objectstring相关的方式不相关。 One does not inherit from the other. 一个不继承另一个。 Rather, they are different variants of a generic type definition. 相反,它们是泛型类型定义的不同变体。 In the generic world, you can't assign one to the other. 在通用世界中,您不能将一个分配给另一个。 The same is true of this: 对此也是如此:

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

In this example, T could be BaseParamClass or one of its derived types. 在此示例中, T可以是BaseParamClass或其派生类型之一。 They are not the same type. 它们不是同一类型。 So to remain type-safe, the compiler has to disallow this assignment, and your Register call, which has the same type mismatch. 因此,为了保持类型安全,编译器必须禁止此赋值,并且您的Register调用具有相同的类型不匹配。

Standard ways around this 这方面的标准方法

You need a covariant interface. 你需要一个协变的接口。 These allow assignment from derived to base. 这些允许从派生到基础的分配。 So for example, while this is still illegal: 例如,虽然这仍然是非法的:

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

This is totally fine: 这很好:

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

Because IEnumerable was declared to be covariant, like this: 因为IEnumerable被声明为协变,如下所示:

interface IEnumerable<out T> 

Which means it is can be assigned in this way. 这意味着它可以通过这种方式分配。 It works because using out also adds a compiler constraint to the interface: it can be used to retrieve stuff... 它的工作原理是因为using out还为接口添加了一个编译器约束:它可以用来检索东西......

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

...but it cannot accept anything: ......但它不能接受任何东西:

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

These constraints are what allow generics to provide early-bound type safety while still allowing certain forms of (non-inheritance) polymorphism. 这些约束允许泛型提供早期类型的安全性,同时仍允许某些形式的(非继承)多态性。

What I'd suggest 我建议的是什么

Based on your comment, it sounds like you just need a container (a stack, apparently) that can hold references to these BaseClass<T> instances. 根据您的评论,听起来您只需要一个可以保存对这些BaseClass<T>实例的引用的容器(显然是一个堆栈)。 If you are following separation of concerns, the stack doesn't need to actually do anything with the T , other than store it and retrieve it, and to allow it to register itself. 如果你关注的是关注点,那么堆栈不需要实际对T做任何事情,除了存储它并检索它,并允许它自己注册。

Since that is a separate concern, make a separate interface. 由于这是一个单独的问题,请创建一个单独的界面。

And in the interest of keeping things simple, maybe avoid using generics completely for this bit. 为了保持简单,可以避免完全使用泛型。

One way to do it-- 一种方法 -

Create an interface that allows access to everything the stack needs to know about an item it is containing. 创建一个接口,允许访问堆栈需要知道的包含它的项目的所有内容。 For example, if the stack contains popups of various kinds, you may want to expose the popup's title. 例如,如果堆栈包含各种弹出窗口,您可能希望公开弹出窗口的标题。

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

Now use it like this: 现在使用它像这样:

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; }
}

Unless there is some other requirement, you shouldn't need to make it any more complicated than that. 除非有其他要求,否则你不应该让它变得更复杂。

All you really need to do is add an interface. 您真正需要做的就是添加一个界面。 This works: 这有效:

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 DotNetFiddle上的工作代码

I have another option for you. 我有另一种选择。

Let's split the BaseClass<T> class into two with a non-generic base, like so: 让我们将BaseClass<T>类拆分为两个非泛型基类,如下所示:

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!
    }
}

Now, the list inside ListHandler can be defined as private List<BaseClass> list; 现在, ListHandlerlist可以定义为private List<BaseClass> list; . That means there is no problem adding any BaseClass item to the list. 这意味着将任何BaseClass项添加到列表中都没有问题。 We also can then define two methods for registering and fetching generic versions of the BaseClass<T> from the ListHandler . 然后,我们还可以定义两种方法,用于从ListHandler注册和获取BaseClass<T>通用版本。 It would look like this: 它看起来像这样:

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();
    }
}

So, given a class public class FooParam : BaseParamClass { } I can write this code: 所以,给定一个类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));

The result from this code is True is written to the console - which means I can successfully fetch the instance of BaseClass<FooParam> from the ListHandler . 此代码的结果是True写入控制台 - 这意味着我可以从ListHandler成功获取BaseClass<FooParam>ListHandler

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM