Прежде всего, я бы отделил анализатор пакетов от считывателя потока данных (чтобы я мог писать тесты, не имея дело с потоком).Затем рассмотрим базовый класс, который предоставляет метод для чтения в пакете и один для записи пакета.
Кроме того, я бы создал словарь (только один раз, а затем повторно использовал его для будущих вызовов), как показано ниже:
class Program {
static void Main(string[] args) {
var assembly = Assembly.GetExecutingAssembly();
IDictionary<byte, Func<Message>> messages = assembly
.GetTypes()
.Where(t => typeof(Message).IsAssignableFrom(t) && !t.IsAbstract)
.Select(t => new {
Keys = t.GetCustomAttributes(typeof(AcceptsAttribute), true)
.Cast<AcceptsAttribute>().Select(attr => attr.MessageId),
Value = (Func<Message>)Expression.Lambda(
Expression.Convert(Expression.New(t), typeof(Message)))
.Compile()
})
.SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value }))
.ToDictionary(o => o.Key, v => v.Value);
//will give you a runtime error when created if more
//than one class accepts the same message id, <= useful test case?
var m = messages[5](); // consider a TryGetValue here instead
m.Accept(new Packet());
Console.ReadKey();
}
}
[Accepts(5)]
public class FooMessage : Message {
public override void Accept(Packet packet) {
Console.WriteLine("here");
}
}
//turned off for the moment by not accepting any message ids
public class BarMessage : Message {
public override void Accept(Packet packet) {
Console.WriteLine("here2");
}
}
public class Packet {}
public class AcceptsAttribute : Attribute {
public AcceptsAttribute(byte messageId) { MessageId = messageId; }
public byte MessageId { get; private set; }
}
public abstract class Message {
public abstract void Accept(Packet packet);
public virtual Packet Create() { return new Packet(); }
}
Редактировать: Некоторые объяснения того, что здесь происходит:
Первый:
[Accepts(5)]
Эта строка является атрибутом C # (определяется AcceptsAttribute
) говорит, что класс FooMessage
принимает идентификатор сообщения, равный 5.
Секунда:
Да, словарь создается во время выполнения с помощью отражения.Вам нужно сделать это только один раз (я бы поместил его в одноэлементный класс, в который можно поместить тестовый пример, который можно запустить, чтобы убедиться в правильности построения словаря).
Третий:
var m = messages[5]();
Эта строка получает следующее скомпилированное лямбда-выражение из словаря и выполняет его:
()=>(Message)new FooMessage();
(приведение необходимо в .NET 3.5, но не в 4.0 из-за ковариантных изменений в том, какДелагат работает, в 4.0 объект типа Func<FooMessage>
может быть назначен объекту типа Func<Message>
.)
Это лямбда-выражение строится линией присваивания значения во время создания словаря:
Value = (Func<Message>)Expression.Lambda(Expression.Convert(Expression.New(t), typeof(Message))).Compile()
(приведение здесь необходимо для приведения скомпилированного лямбда-выражения к Func<Message>
.)
Я сделал это таким образом, потому что у меня уже есть тип, доступный мне на тот момент.Вы также можете использовать:
Value = ()=>(Message)Activator.CreateInstance(t)
Но я считаю, что это будет медленнее (и приведение здесь необходимо изменить Func<object>
на Func<Message>
).
Четвертый:
.SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value }))
Это было сделано, потому что я чувствовал, что вы могли бы иметь значение при размещении AcceptsAttribute
более одного раза в классе (чтобы принять более одного идентификатора сообщения на класс).Это также имеет приятный побочный эффект игнорирования классов сообщений, которые не имеют атрибута идентификатора сообщения (в противном случае метод Where должен был бы иметь сложность определения наличия атрибута).