首先,我将数据包解析器与数据流读取器分开(这样我就可以编写测试而不处理流)。然后考虑一个基类,它提供了一种读取数据包的方法和一种写入数据包的方法。
另外,我会构建一个字典(仅一次,然后将其用于将来的调用),如下所示:
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(); }
}
编辑:对这里发生的事情的一些解释:
First:
[Accepts(5)]
这一行是一个 C# 属性(定义为AcceptsAttribute
)说FooMessage
类接受消息 ID 5。
Second:
是的,字典是在运行时通过反射构建的。您只需要执行一次(我会将其放入一个单例类中,您可以在其上放置一个测试用例,该测试用例可以运行以确保字典正确构建)。
Third:
var m = messages[5]();
此行从字典中获取以下已编译的 lambda 表达式并执行它:
()=>(Message)new FooMessage();
(强制转换在 .NET 3.5 中是必要的,但在 4.0 中则不需要,因为延迟工作方式发生了协变变化,在 4.0 中,类型的对象Func<FooMessage>
可以分配给该类型的对象Func<Message>
.)
该 lambda 表达式是在字典创建期间由 Value 赋值行构建的:
Value = (Func<Message>)Expression.Lambda(Expression.Convert(Expression.New(t), typeof(Message))).Compile()
(此处的强制转换是将已编译的 lambda 表达式强制转换为Func<Message>
.)
我这样做是因为当时我碰巧已经有了可用的类型。您还可以使用:
Value = ()=>(Message)Activator.CreateInstance(t)
但我相信会比较慢(而且这里的演员阵容有必要改变)Func<object>
into Func<Message>
).
Fourth:
.SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value }))
这样做是因为我觉得您将AcceptsAttribute
在一个班级上多次(每个班级接受多个消息 ID)。这还有一个很好的副作用,即忽略没有消息 id 属性的消息类(否则Where 方法需要具有确定该属性是否存在的复杂性)。