Приведение в общий тип в С#
У меня есть словарь для сопоставления определенного типа определенному универсальному объекту для этого типа. Например:
typeof(LoginMessage) maps to MessageProcessor<LoginMessage>
Теперь проблема состоит в том, чтобы получить этот универсальный объект во время выполнения из Словаря. Или, если быть более конкретным: направить восстановленный объект на определенный тип общего типа.
Мне нужно, чтобы он работал примерно так:
Type key = message.GetType();
MessageProcessor<key> processor = messageProcessors[key] as MessageProcessor<key>;
Надеюсь, что это будет легкое решение.
Изменить:
Я не хочу использовать Ifs и переключатели. Из-за проблем с производительностью я также не могу использовать какое-либо отражение.
Ответы
Ответ 1
Это работает для вас?
interface IMessage
{
void Process(object source);
}
class LoginMessage : IMessage
{
public void Process(object source)
{
}
}
abstract class MessageProcessor
{
public abstract void ProcessMessage(object source, object type);
}
class MessageProcessor<T> : MessageProcessor where T: IMessage
{
public override void ProcessMessage(object source, object o)
{
if (!(o is T)) {
throw new NotImplementedException();
}
ProcessMessage(source, (T)o);
}
public void ProcessMessage(object source, T type)
{
type.Process(source);
}
}
class Program
{
static void Main(string[] args)
{
Dictionary<Type, MessageProcessor> messageProcessors = new Dictionary<Type, MessageProcessor>();
messageProcessors.Add(typeof(string), new MessageProcessor<LoginMessage>());
LoginMessage message = new LoginMessage();
Type key = message.GetType();
MessageProcessor processor = messageProcessors[key];
object source = null;
processor.ProcessMessage(source, message);
}
}
Это дает вам правильный объект. Единственное, что я не уверен в том, достаточно ли в вашем случае иметь его как абстрактный MessageProcessor.
Изменить: я добавил интерфейс IMessage. Фактический код обработки теперь должен стать частью различных классов сообщений, которые должны реализовать весь этот интерфейс.
Ответ 2
Следующее, похоже, работает, и оно немного короче, чем другие ответы:
T result = (T)Convert.ChangeType(otherTypeObject, typeof(T));
Ответ 3
Вы можете написать метод, который принимает тип как общий параметр:
void GenericProcessMessage<T>(T message)
{
MessageProcessor<T> processor = messageProcessors[typeof(T)]
as MessageProcessor<T>;
// Call method processor or whatever you need to do
}
Затем вам нужен способ вызова метода с правильным общим аргументом. Вы можете сделать это с отражением:
public void ProcessMessage(object message)
{
Type messageType = message.GetType();
MethodInfo method = this.GetType().GetMethod("GenericProcessMessage");
MethodInfo closedMethod = method.MakeGenericMethod(messageType);
closedMethod.Invoke(this, new object[] {message});
}
Ответ 4
Type type = typeof(MessageProcessor<>).MakeGenericType(key);
Это лучшее, что вы можете сделать, однако, не зная, какой тип это, действительно, вы не можете сделать больше с ним.
EDIT: Я должен уточнить. Я изменил с типа var на тип типа. Я хочу сказать, теперь вы можете сделать что-то вроде этого:
object obj = Activator.CreateInstance(type);
obj теперь будет правильным типом, но так как вы не знаете, какой тип "ключ" находится во время компиляции, нет способа его бросить и сделать что-нибудь полезное с ним.
Ответ 5
У меня была аналогичная проблема. У меня есть класс;
Action<T>
обладающее свойством типа T.
Как получить свойство, когда я не знаю T? Я не могу передать в Action < > если я не знаю T.
РЕШЕНИЕ:
Внедрить не общий интерфейс;
public interface IGetGenericTypeInstance
{
object GenericTypeInstance();
}
Теперь я могу передать объект в IGetGenericTypeInstance, а GenericTypeInstance вернет свойство как объект типа.
Ответ 6
Вы не можете этого сделать. Вы можете попытаться рассказать о своей проблеме с более высокой точки зрения (то есть, что именно вы хотите выполнить с литой переменной) для другого решения.
Вы можете пойти с чем-то вроде этого:
public abstract class Message {
// ...
}
public class Message<T> : Message {
}
public abstract class MessageProcessor {
public abstract void ProcessMessage(Message msg);
}
public class SayMessageProcessor : MessageProcessor {
public override void ProcessMessage(Message msg) {
ProcessMessage((Message<Say>)msg);
}
public void ProcessMessage(Message<Say> msg) {
// do the actual processing
}
}
// Dispatcher logic:
Dictionary<Type, MessageProcessor> messageProcessors = {
{ typeof(Say), new SayMessageProcessor() },
{ typeof(string), new StringMessageProcessor() }
}; // properly initialized
messageProcessors[msg.GetType().GetGenericArguments()[0]].ProcessMessage(msg);
Ответ 7
Посмотрите, подходит ли вам следующее решение. Трюк заключается в определении интерфейса базового процессора, который принимает базовый тип сообщения.
interface IMessage
{
}
class LoginMessage : IMessage
{
}
class LogoutMessage : IMessage
{
}
class UnknownMessage : IMessage
{
}
interface IMessageProcessor
{
void PrcessMessageBase(IMessage msg);
}
abstract class MessageProcessor<T> : IMessageProcessor where T : IMessage
{
public void PrcessMessageBase(IMessage msg)
{
ProcessMessage((T)msg);
}
public abstract void ProcessMessage(T msg);
}
class LoginMessageProcessor : MessageProcessor<LoginMessage>
{
public override void ProcessMessage(LoginMessage msg)
{
System.Console.WriteLine("Handled by LoginMsgProcessor");
}
}
class LogoutMessageProcessor : MessageProcessor<LogoutMessage>
{
public override void ProcessMessage(LogoutMessage msg)
{
System.Console.WriteLine("Handled by LogoutMsgProcessor");
}
}
class MessageProcessorTest
{
/// <summary>
/// IMessage Type and the IMessageProcessor which would process that type.
/// It can be further optimized by keeping IMessage type hashcode
/// </summary>
private Dictionary<Type, IMessageProcessor> msgProcessors =
new Dictionary<Type, IMessageProcessor>();
bool processorsLoaded = false;
public void EnsureProcessorsLoaded()
{
if(!processorsLoaded)
{
var processors =
from processorType in Assembly.GetExecutingAssembly().GetTypes()
where processorType.IsClass && !processorType.IsAbstract &&
processorType.GetInterface(typeof(IMessageProcessor).Name) != null
select Activator.CreateInstance(processorType);
foreach (IMessageProcessor msgProcessor in processors)
{
MethodInfo processMethod = msgProcessor.GetType().GetMethod("ProcessMessage");
msgProcessors.Add(processMethod.GetParameters()[0].ParameterType, msgProcessor);
}
processorsLoaded = true;
}
}
public void ProcessMessages()
{
List<IMessage> msgList = new List<IMessage>();
msgList.Add(new LoginMessage());
msgList.Add(new LogoutMessage());
msgList.Add(new UnknownMessage());
foreach (IMessage msg in msgList)
{
ProcessMessage(msg);
}
}
public void ProcessMessage(IMessage msg)
{
EnsureProcessorsLoaded();
IMessageProcessor msgProcessor = null;
if(msgProcessors.TryGetValue(msg.GetType(), out msgProcessor))
{
msgProcessor.PrcessMessageBase(msg);
}
else
{
System.Console.WriteLine("Processor not found");
}
}
public static void Test()
{
new MessageProcessorTest().ProcessMessages();
}
}
Ответ 8
Это просто не разрешено:
Type key = message.GetType();
MessageProcessor<key> processor = messageProcessors[key] as MessageProcessor<key>;
Вы не можете получить общий тип как значение переменной.
Вам нужно было бы сделать переключатель или что-то еще:
Type key = message.GetType();
if (key == typeof(Foo))
{
MessageProcessor<Foo> processor = (MessageProcessor<Foo>)messageProcessors[key];
// Do stuff with processor
}
else if (key == typeof(Bar))
{
MessageProcessor<bar> processor = (MessageProcessor<Bar>)messageProcessors[key];
// Do stuff with processor
}
...
Ответ 9
Как уже упоминалось, вы не можете использовать его напрямую. Одно из возможных решений заключается в том, чтобы эти родовые типы наследовались от не-общего интерфейса, и в этом случае вы все равно можете ссылаться на методы без него. Используя отражение, вы можете передать отображаемый объект любому ожидающему его методу, тогда выполнение будет выполнено для вас. Поэтому, если у вас есть метод под названием Accept, ожидающий
MessageProcessor как параметр, вы можете найти его и вызвать его динамически.
Ответ 10
public delegate void MessageProcessor<T>(T msg) where T : IExternalizable;
virtual public void OnRecivedMessage(IExternalizable msg)
{
Type type = msg.GetType();
ArrayList list = processors.Get(type);
if (list != null)
{
object[] args = new object[]{msg};
for (int i = list.Count - 1; i >= 0; --i)
{
Delegate e = (Delegate)list[i];
e.Method.Invoke(e.Target, args);
}
}
}
Ответ 11
Ответ @DanielPlaisted до работы обычно работает, но общий метод должен быть общедоступным или нужно использовать BindingFlags.NonPublic | BindingFlags.Instance
! Не удалось опубликовать его в качестве комментария за отсутствие репутации.
Ответ 12
Я попытался решить подобную проблему вокруг классов таблиц данных вместо сообщений. Корневая проблема, упомянутая выше, в том, что отличная версия класса для генерируемой версии не была общей, была одинаковой.
Чтобы включить инъекцию в переносимую библиотеку классов, которая не поддерживает библиотеки баз данных, я представил набор интерфейсных классов с намерением, чтобы я мог передать тип и получить соответствующий общий. Это привело к необходимости использования универсального метода.
// Interface for injection
public interface IDatabase
{
// Original, non-functional signature:
IDatatable<object> GetDataTable(Type dataType);
// Functional method using a generic method:
IDatatable<T> GetDataTable<T>();
}
И это вся реализация с использованием общего метода выше.
Общий класс, который будет выведен из словаря.
// Non-generic base class allows listing tables together
abstract class Datatable
{
Datatable(Type storedClass)
{
StoredClass = storedClass;
}
Type StoredClass { get; private set; }
}
// Generic inheriting class
abstract class Datatable<T>: Datatable, IDatatable<T>
{
protected Datatable()
:base(typeof(T))
{
}
}
Это класс, который хранит общий класс и передает его для удовлетворения общего метода в интерфейсе
class Database
{
// Dictionary storing the classes using the non-generic base class
private Dictionary<Type, Datatable> _tableDictionary;
protected Database(List<Datatable> tables)
{
_tableDictionary = new Dictionary<Type, Datatable>();
foreach (var table in tables)
{
_tableDictionary.Add(table.StoredClass, table);
}
}
// Interface implementation, casts the generic
public IDatatable<T> GetDataTable<T>()
{
Datatable table = null;
_tableDictionary.TryGetValue(typeof(T), out table);
return table as IDatatable<T>;
}
}
И, наконец, вызов метода интерфейса.
IDatatable<CustomerAccount> table = _database.GetDataTable<CustomerAccount>();