簡體   English   中英

C#泛型類型不可分配

[英]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 ,這顯然不是真的,這就是為什么你可以'做這個轉換。

我現在能想到的兩種解決方案都不是很好。

  1. 使字典無類型化( IDictionary<Type, IList> ,甚至是IDictionary<Type, object> )並強制轉換為IList<ICommandHandler<TCommand>> ,這將始終有效,因為您只在類型時向其添加命令關鍵比賽。

  2. 創建一個接受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.

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