Zuerst würde ich den Paket-Parser vom Datenstromleser trennen (damit ich Tests schreiben konnte, ohne mit dem Strom zu handeln). Betrachten Sie dann eine Basisklasse, die eine Methode zum Einlesen eines Pakets und eine zum Schreiben eines Pakets bereitstellt.
Zusätzlich würde ich ein Wörterbuch bauen (einmal nur dann für zukünftige Anrufe wiederverwendet werden) wie folgt aus:
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: Einige Erklärungen, was hier vor sich geht:
Erstens:
[Accepts(5)]
Diese Linie ist ein C# Attribut (definiert durch AcceptsAttribute
) Sagt, die die FooMessage
Klasse die Message-ID 5.
Zweite akzeptiert:
Ja das Wörterbuch wird gebaut zur Laufzeit über Reflexion. Sie müssen dies nur einmal tun (ich würde es in eine Singleton-Klasse einfügen, in die Sie einen Testfall einfügen können, der ausgeführt werden kann, um sicherzustellen, dass das Wörterbuch korrekt erstellt wird).
Drittens:
var m = messages[5]();
Diese Zeile wird die kompilierte folgenden Lambda-Ausdruck des Wörterbuchs aus und führt es aus:
()=>(Message)new FooMessage();
(Die Besetzung ist notwendig in .NET 3.5, aber nicht in 4.0 durch zu den kovarianten Änderungen, wie Absetzarbeiten funktionieren, kann in 4.0 ein Objekt vom Typ Func<FooMessage>
einem Objekt des Typs Func<Message>
zugeordnet werden.)
Dieses Lambda-Ausdruck wird durch die Wertzuweisung Leitung während Wörterbucherstellung gebaut:
Value = (Func<Message>)Expression.Lambda(Expression.Convert(Expression.New(t), typeof(Message))).Compile()
(Die Besetzung ist hier notwendig, um den kompilierten Lambda-Ausdruck zu Func<Message>
werfen.)
ich, dass diese Art und Weise getan, weil ich zufällig den Typ, der mir zu diesem Zeitpunkt zur Verfügung steht. Sie können auch verwenden:
Value =()=>(Message)Activator.CreateInstance(t)
Aber ich glaube, dass langsamer sein würde (und die Besetzung ist hier notwendig Func<object>
in Func<Message>
zu ändern).
Vierter:
.SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value }))
Dies geschieht, weil ich das Gefühl, dass Sie Wert bei der Platzierung des AcceptsAttribute
mehr als einmal auf einer Klasse haben könnten (mehr als eine Nachrichten-ID pro Klasse zu akzeptieren). Dies hat auch den positiven Nebeneffekt, dass Nachrichtenklassen ignoriert werden, die kein Message-ID-Attribut haben (andernfalls müsste die Where-Methode die Komplexität haben, zu bestimmen, ob das Attribut vorhanden ist).
Hallo Bill, Danke für diese Antwort. Ich versuche, mich darum zu kümmern! Was bedeutet ... [Akzeptiert (5)] ... Notation bedeuten? Wird das Wörterbuch zur Laufzeit mit Reflektion aufgefüllt? – Prembo
Danke für diese detaillierte Erklärung. Ich habe viel gelernt! Es ist eine sehr elegante und skalierbare Lösung. Ausgezeichnet. I – Prembo