Trước hết, tôi sẽ tách trình phân tích cú pháp gói khỏi trình đọc luồng dữ liệu (để tôi có thể viết các bài kiểm tra mà không cần xử lý luồng). Sau đó xem xét một lớp cơ sở cung cấp một phương thức để đọc trong một gói và một để viết một gói.
Bên cạnh đó tôi sẽ xây dựng một từ điển (một lần duy nhất sau đó tái sử dụng nó cho các cuộc gọi trong tương lai) như sau:
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(); }
}
Edit: Một số giải thích về những gì đang xảy ra ở đây:
Trước tiên:
[Accepts(5)]
Đường này là thuộc tính C# (được xác định bởi AcceptsAttribute
)) Cho biết lớp FooMessage
chấp nhận id của thông điệp của 5.
Thứ hai:
Có từ điển đang được xây dựng trong thời gian chạy qua phản ánh. Bạn chỉ cần làm điều này một lần (tôi sẽ đặt nó vào một lớp singleton mà bạn có thể đặt một trường hợp thử nghiệm trên nó có thể được chạy để đảm bảo rằng từ điển xây dựng chính xác).
Thứ ba:
var m = messages[5]();
Dòng này được biểu thức lambda biên soạn sau đây ra khỏi từ điển và thực hiện nó:
()=>(Message)new FooMessage();
(Dàn diễn viên là cần thiết trong .NET 3.5 nhưng không phải trong 4,0 do với những thay đổi về biến đổi trong cách làm việc của delagates, trong 4.0 đối tượng thuộc loại Func<FooMessage>
có thể được gán cho một đối tượng thuộc loại Func<Message>
.)
biểu thức lambda này được xây dựng bởi các dòng phân Giá trị trong quá trình tạo từ điển:
Value = (Func<Message>)Expression.Lambda(Expression.Convert(Expression.New(t), typeof(Message))).Compile()
(Dàn diễn viên ở đây là cần thiết để đúc biểu thức lambda biên soạn để Func<Message>
.)
tôi đã làm điều đó theo cách này bởi vì tôi xảy ra đã có sẵn loại cho tôi tại thời điểm đó. Bạn cũng có thể sử dụng:
Value =()=>(Message)Activator.CreateInstance(t)
Nhưng tôi tin rằng sẽ chậm hơn (và các diễn viên ở đây là cần thiết để thay đổi Func<object>
vào Func<Message>
).
Thứ tư:
.SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value }))
này được thực hiện bởi vì tôi cảm thấy rằng bạn có thể có giá trị trong việc đặt càng AcceptsAttribute
hơn một lần trên một lớp (để chấp nhận nhiều hơn một id của thông điệp mỗi lớp). Điều này cũng có tác dụng phụ tốt đẹp của việc bỏ qua các lớp thông báo không có thuộc tính id thư (nếu không phương thức Where sẽ cần phải có độ phức tạp để xác định nếu thuộc tính có mặt).
Xin chào Bill, Cảm ơn câu trả lời đó. Tôi đang cố gắng để có được đầu của tôi xung quanh nó! Làm thế nào để ... [Chấp nhận (5)] ... ký hiệu có nghĩa là gì? Từ điển có được phản ánh theo thời gian chạy không? – Prembo
Cảm ơn lời giải thích chi tiết đó. Tôi đã học được rất nhiều! Đó là một giải pháp rất thanh lịch và có khả năng mở rộng. Xuất sắc. I – Prembo