[英]C# generic type not assignable
我想用泛型編寫一個CommandProcessor 。 這個想法是,一個命令是通過一個單一的對象發出(在CommandProcessor本身)然后識別處理該給定的命令的命令處理程序 。
但是,以下代碼無法編譯,我無法理解原因:
class GenericCommandProcessor : ICommandProcessor
{
private readonly IDictionary<Type, IList<ICommandHandler<ICommand>>> _handlers =
new Dictionary<Type, IList<ICommandHandler<ICommand>>>();
public void Register<TCommand>(ICommandHandler<TCommand> handler)
where TCommand : ICommand
{
IList<ICommandHandler<ICommand>> handlers = GetHandlers<TCommand>();
handlers.Add(handler); // <-- This doesn't compile
}
public void Process<TCommand>(TCommand command)
where TCommand : ICommand
{
IList<ICommandHandler<ICommand>> handlers = GetHandlers<TCommand>();
foreach (var commandHandler in handlers)
{
commandHandler.Handle(command);
}
}
private IList<ICommandHandler<ICommand>> GetHandlers<TCommand>()
{
Type commandType = typeof(TCommand);
IList<ICommandHandler<ICommand>> handlers;
if (!_handlers.TryGetValue(commandType, out handlers))
{
handlers = new List<ICommandHandler<ICommand>>();
_handlers.Add(commandType, handlers);
}
return handlers;
}
}
這是不編譯的行:
handlers.Add(handler);
編譯器返回以下錯誤:
cannot convert from 'GenericCommandHandlerTest.ICommandHandler<TCommand>' to 'GenericCommandHandlerTest.ICommandHandler<GenericCommandHandlerTest.ICommand>'
我希望它,因為Register()
有一個通用約束:
where TCommand : ICommand
我通過從IoC(在我的案例中使用Castle Windsor)中解析命令處理程序列表來避免這個問題,而不是使用已注冊處理程序列表的字典,但我很想理解為什么在CLR級別此代碼沒有編譯。 我想我看不到樹上的木頭......
提前謝謝了。
只需將您的方法更改為:
public void AddListItem(IListItem listItem)
{
_items.Add(listItem);
}
這里不需要使用泛型。
正如其他人已經說過的那樣:即使沒有更改,您的代碼也會編譯,因此請更新您的示例代碼。
修復示例后更新 :
您不能將ICommandHandler<TCommand>
類型的變量添加到IList<ICommandHandler<ICommand>>
,因為ICommandHandler<ICommand>
和ICommandHandler<TCommand>
是兩種不同的類型,盡管TCommand
實現了ICommand
。 如果它可以工作,我的第一個答案將再次正確,你不需要首先使你的方法通用。
我猜Covariance在這里會有所幫助,但不幸的是,在這種情況下它不受支持。
編輯
正如丹尼爾所說,你所尋找的是協方差。
如果您使用的是C#4,這實際上是存在的,但不是一個有用的形式。 要使此示例正常工作, ICommandHandler
需要具有逆變性。 如果TCommand在ICommandHandler
只是一個out
參數,你可以將ICommandHandler
定義為
interface ICommandHandler<out TCommand> where TCommand: ICommand { ... }
然后你就可以在List<ICommandHandler<ICommand>>
存儲ICommandHandler<TCommand>
,因為ICommandHandler<TCommand>
可以安全地轉換為ICommandHandler<ICommand>
- 我們知道如果某些東西返回TCommand
,它會返回ICommand
。
但是,在您的情況下, TCommand
是一個in
參數,要將ICommandHandler<TCommand>
轉換為ICommandHandler<ICommand>
您需要知道每個ICommand
都是一個TCommand
,這顯然不是真的,這就是為什么你可以'做這個轉換。
我現在能想到的兩種解決方案都不是很好。
使字典無類型化( IDictionary<Type, IList>
,甚至是IDictionary<Type, object>
)並強制轉換為IList<ICommandHandler<TCommand>>
,這將始終有效,因為您只在類型時向其添加命令關鍵比賽。
創建一個接受ICommandHandler<TCommand>
的ICommandHandlerWrapper
,因為它本身就是一個ICommandHandler<ICommand>
; 調用方法時,它會進行類型檢查並調用基礎值。
舊版本
可能你錯過了代碼示例中的重要內容。
但我確實想問,為什么要使用
public void AddListItem<T>(T listItem)
where T : IListItem
{
_items.Add(listItem);
}
並不是
public void AddListItem(IListItem listItem)
{
_items.Add(listItem);
}
從根本上說,問題在於你試圖將編譯時安全與你無法表達的概念混合在一起:字典將typeof(T)
映射到與T
相關的東西。 在類似的情況下,我發現圍繞編譯時安全性設計API很有用,但保留一小部分需要強制轉換的代碼。 在這種情況下,這只需要是GetHandlers()
方法(順便說一句,這不是線程安全的 - 我希望這無關緊要)。
這是完整的課程:
class GenericCommandProcessor : ICommandProcessor
{
private readonly IDictionary<Type, object> _handlers =
new Dictionary<Type, object>();
public void Register<TCommand>(ICommandHandler<TCommand> handler)
where TCommand : ICommand
{
IList<ICommandHandler<TCommand>> handlers = GetHandlers<TCommand>();
handlers.Add(handler);
}
public void Process<TCommand>(TCommand command)
where TCommand : ICommand
{
IList<ICommandHandler<TCommand>> handlers = GetHandlers<TCommand>();
foreach (var commandHandler in handlers)
{
commandHandler.Handle(command);
}
}
private IList<ICommandHandler<TCommand>> GetHandlers<TCommand>()
{
Type commandType = typeof(TCommand);
object untypedValue;
if (!_handlers.TryGetValue(commandType, out untypedValue))
{
untypedValue = new List<ICommandHandler<TCommand>>();
_handlers.Add(commandType, untypedValue);
}
return (IList<ICommandHandler<TCommand>>) untypedValue;
}
}
您可能會將字典的TValue
類型參數更改為更具限制性的內容(例如IList
),但實際上並沒有太大的區別。
我做了以下並且能夠編譯。
public interface IListItem
{
int MyProperty { get; set; }
}
public class ListProvider
{
private IList<IListItem> _items = new List<IListItem>();
public void AddListItem<T>(T listItem) where T : IListItem
{
_items.Add(listItem);
}
}
對我來說工作也很好
class Program
{
private static readonly IList<IListItem> _items = new List<IListItem>();
public static void AddListItem<T>(T listItem) where T : IListItem
{
_items.Add(listItem);
foreach (var item in _items)
{
Console.WriteLine(item.ToString());
}
Console.ReadLine();
}
static void Main(string[] args)
{
Test tester = new Test();
AddListItem(tester);
}
}
internal interface IListItem
{
}
public class Test : IListItem
{
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.