簡體   English   中英

C#將當前泛型類實例傳遞給其他類

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

最近我開始學習泛型。 我遇到了存儲對泛型類實例的引用的麻煩。 如您所見,我的類ListHandler可以存儲對特定類型的BaseClass的引用。 我很樂意自己注冊BaseClass實例,這就是為什么我要保證他們將通過添加'where'來使用BaseParamClass。 無論如何 - 它不會編譯。'這個',即使在課堂上使用'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
    }
}

為什么不使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<T>包含對引用BaseClass<T>本身的類的引用似乎很奇怪。

為什么你的代碼不能編譯

為了完全理解為什么你的代碼不能編譯,你將不得不深入研究協方差和逆變 ,這是一個很大的主題,很難在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> 

這意味着它可以通過這種方式分配。 它的工作原理是因為using out還為接口添加了一個編譯器約束:它可以用來檢索東西......

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

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

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

這些約束允許泛型提供早期類型的安全性,同時仍允許某些形式的(非繼承)多態性。

我建議的是什么

根據您的評論,聽起來您只需要一個可以保存對這些BaseClass<T>實例的引用的容器(顯然是一個堆棧)。 如果你關注的是關注點,那么堆棧不需要實際對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; }
}

除非有其他要求,否則你不應該讓它變得更復雜。

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

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

DotNetFiddle上的工作代碼

我有另一種選擇。

讓我們將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!
    }
}

現在, ListHandlerlist可以定義為private List<BaseClass> list; 這意味着將任何BaseClass項添加到列表中都沒有問題。 然后,我們還可以定義兩種方法,用於從ListHandler注冊和獲取BaseClass<T>通用版本。 它看起來像這樣:

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<FooParam>ListHandler

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM