Прежде всего, я бы отделил парсер пакетов от считывателя потока данных (чтобы я мог писать тесты, не имея дела с потоком). Затем рассмотрим базовый класс, который предоставляет метод для чтения в пакете и один для записи пакета.
Кроме того, я бы построил словарь (только один раз, а затем повторно использовал его для будущих вызовов), например следующий:
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 должен был бы иметь сложность определения наличия атрибута).
person
Community
schedule
19.07.2010