[英]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
那是因為這兩個列表的關聯方式與object
和string
相關的方式不相關。 一個不繼承另一個。 相反,它們是泛型類型定義的不同變體。 在通用世界中,您不能將一個分配給另一個。 對此也是如此:
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);
}
}
我有另一種選擇。
讓我們將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!
}
}
現在, ListHandler
的list
可以定義為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.