C # generic type not assigned

I want to write CommandProcessor using generics. The idea is that the command is issued through a single object (CommandProcessor itself), which then identifies the command handlers that process this command.

However, the following code does not compile, and I could not understand why:

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

This is the line that does not compile:

 handlers.Add(handler); 

The compiler returns the following error:

 cannot convert from 'GenericCommandHandlerTest.ICommandHandler<TCommand>' to 'GenericCommandHandlerTest.ICommandHandler<GenericCommandHandlerTest.ICommand>' 

I would expect this because Register() has a common limitation:

 where TCommand : ICommand 

I avoided this problem by resolving the list of command handlers from IoC (Castle Windsor in my case) in favor of having a dictionary of lists of registered handlers, but I would like to understand why, at the CLR level, this code does not compile. I think that I just do not see a tree for trees ...

Thank you very much in advance.

+4
source share
5 answers

Just change your method to this:

 public void AddListItem(IListItem listItem) { _items.Add(listItem); } 

No need to use generics here.

As others have said: even without modification, your code compiles, so please update your sample code.

Update after fixing your example:
You cannot add a variable of type ICommandHandler<TCommand> to IList<ICommandHandler<ICommand>> because ICommandHandler<ICommand> and ICommandHandler<TCommand> are two different types, although TCommand implements ICommand . If that works, my first answer will be correct, and you won’t need to generate your method in the first place.

I think Covariance will be useful here, but unfortunately in this case it is not supported.

+4
source

Edit

What you are looking for is, as Daniel said, covariance.

If you use C # 4, it really exists, but not in a useful way for you. For this example to work, ICommandHandler must be contravariant. If TCommand was always an out parameter in ICommandHandler , and you would define ICommandHandler as

 interface ICommandHandler<out TCommand> where TCommand: ICommand { ... } 

then you can save ICommandHandler<TCommand> to List<ICommandHandler<ICommand>> , because ICommandHandler<TCommand> can be safely assigned to ICommandHandler<ICommand> - we know that if TCommand returns TCommand , it returns ICommand .

However, in your case, TCommand is an in parameter and to convert ICommandHandler<TCommand> to ICommandHandler<ICommand> you need to know that each ICommand is TCommand , which is obviously not true, so you cannot do this conversion.

None of the two solutions that I now think of is enough.

  • Make an untyped dictionary (a IDictionary<Type, IList> or even IDictionary<Type, object> ) and add to IList<ICommandHandler<TCommand>> , which will always work because you add only a command to it when the type matches.

  • Make an ICommandHandlerWrapper that accepts ICommandHandler<TCommand> as is ICommandHandler<ICommand> ; when a method is called, it performs a type check and calls the base value.


Old version

Works on my machine

Perhaps your code example is missing something important.

But I want to ask why use

 public void AddListItem<T>(T listItem) where T : IListItem { _items.Add(listItem); } 

but not

 public void AddListItem(IListItem listItem) { _items.Add(listItem); } 
+3
source

Basically the problem is that you are trying to mix security at compile time with a concept that you cannot express: the fact that the dictionary maps typeof(T) to something related to T In such cases, I found it useful to develop an API to ensure compile-time security, but keep a little code that requires casting. In this case, it should be only the GetHandlers() method (which, incidentally, is not thread safe), I hope this does not matter.

Here's the full class:

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

You could change the argument of the TValue type of the dictionary to something more restrictive (for example, IList ), but actually it would not make much difference.

+2
source

I did the following and was able to compile.

 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); } } 
+1
source

Works great for me too

 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 { } 
0
source

Source: https://habr.com/ru/post/1344273/


All Articles