La différence de performance entre ces deux approches devrait être négligeable. Personnellement, je suggère d'utiliser une approche non statique en raison de la flexibilité qu'elle offre. Si vous trouvez utile de regrouper une grande partie de la logique d'analyse en un seul endroit, vous pouvez utiliser une approche combinée (illustrée dans mon exemple ci-dessous). En ce qui concerne les performances, Si vous créez plusieurs instances de votre classe Analyser à plusieurs reprises, vous remarquerez peut-être un faible impact sur les performances, mais vous pourrez probablement refactoriser le code pour éviter de créer plusieurs fois des instances de la classe Parser. De même, l'appel d'une méthode d'instance (en particulier une méthode virtuelle) n'est techniquement pas aussi rapide que l'appel d'une méthode statique, là encore la différence de performance devrait être très négligeable.
McWafflestix soulève un bon point sur l'état.Cependant, étant donné que votre implémentation actuelle utilise des méthodes statiques, je suppose que votre classe Parser n'a pas besoin de maintenir l'état entre les appels aux méthodes Read et vous devriez donc pouvoir réutiliser la même instance d'analyseur pour analyser plusieurs objets d'un BinaryReader
flux. Ci-dessous est un exemple qui illustre l'approche que je prendrais probablement pour ce problème. Voici quelques caractéristiques de cet exemple:
- Utilisation du polymorphisme pour extraire des détails sur l'emplacement de la logique d'analyse pour un type donné d'objet.
- Utilisation d'un référentiel pour stocker les instances de l'analyseur afin qu'elles puissent être réutilisées. Utilisation de la réflexion pour identifier la logique d'analyse pour une classe ou une structure donnée.
Notez que j'ai gardé la logique d'analyse des méthodes statiques dans la classe ParseHelper
, et les méthodes d'instance Read
sur les classes MyObjectAParser
et MyObjectBParser
utilisent ces méthodes statiques sur la classe ParseHelper
. C'est juste une décision de conception que vous pouvez prendre en fonction de ce qui vous semble le plus logique concernant l'organisation de votre logique d'analyse. Je suppose qu'il serait probablement logique de déplacer une partie de la logique d'analyse spécifique au type dans les classes Parser individuelles, mais conserver une partie de la logique d'analyse générale dans une classe ParseHelper.
// define a non-generic parser interface so that we can refer to all types of parsers
public interface IParser
{
object Read(BinaryReader reader);
}
// define a generic parser interface so that we can specify a Read method specific to a particular type
public interface IParser<T> : IParser
{
new T Read(BinaryReader reader);
}
public abstract class Parser<T> : IParser<T>
{
public abstract T Read(BinaryReader reader);
object IParser.Read(BinaryReader reader)
{
return this.Read(reader);
}
}
// define a Parser attribute so that we can easily determine the correct parser for a given type
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
public class ParserAttribute : Attribute
{
public Type ParserType { get; private set; }
public ParserAttribute(Type parserType)
{
if (!typeof(IParser).IsAssignableFrom(parserType))
throw new ArgumentException(string.Format("The type [{0}] does not implement the IParser interface.", parserType.Name), "parserType");
this.ParserType = parserType;
}
public ParserAttribute(Type parserType, Type targetType)
{
// check that the type represented by parserType implements the IParser interface
if (!typeof(IParser).IsAssignableFrom(parserType))
throw new ArgumentException(string.Format("The type [{0}] does not implement the IParser interface.", parserType.Name), "parserType");
// check that the type represented by parserType implements the IParser<T> interface, where T is the type specified by targetType
if (!typeof(IParser<>).MakeGenericType(targetType).IsAssignableFrom(parserType))
throw new ArgumentException(string.Format("The type [{0}] does not implement the IParser<{1}> interface.", parserType.Name, targetType.Name), "parserType");
this.ParserType = parserType;
}
}
// let's define a couple of example classes for parsing
// the MyObjectA class corresponds to ParseObject1 in the original question
[Parser(typeof(MyObjectAParser))] // the parser type for MyObjectA is MyObjectAParser
class MyObjectA
{
// ...
}
// the MyObjectB class corresponds to ParseObject2 in the original question
[Parser(typeof(MyObjectAParser))] // the parser type for MyObjectB is MyObjectBParser
class MyObjectB
{
// ...
}
// a static class that contains helper functions to handle parsing logic
static class ParseHelper
{
public static MyObjectA ReadObjectA(BinaryReader reader)
{
// <code here to parse MyObjectA from BinaryReader>
throw new NotImplementedException();
}
public static MyObjectB ReadObjectB(BinaryReader reader)
{
// <code here to parse MyObjectB from BinaryReader>
throw new NotImplementedException();
}
}
// a parser class that parses objects of type MyObjectA from a BinaryReader
class MyObjectAParser : Parser<MyObjectA>
{
public override MyObjectA Read(BinaryReader reader)
{
return ParseHelper.ReadObjectA(reader);
}
}
// a parser class that parses objects of type MyObjectB from a BinaryReader
class MyObjectBParser : Parser<MyObjectB>
{
public override MyObjectB Read(BinaryReader reader)
{
return ParseHelper.ReadObjectB(reader);
}
}
// define a ParserRepository to encapsulate the logic for finding the correct parser for a given type
public class ParserRepository
{
private Dictionary<Type, IParser> _Parsers = new Dictionary<Type, IParser>();
public IParser<T> GetParser<T>()
{
// attempt to look up the correct parser for type T from the dictionary
Type targetType = typeof(T);
IParser parser;
if (!this._Parsers.TryGetValue(targetType, out parser))
{
// no parser was found, so check the target type for a Parser attribute
object[] attributes = targetType.GetCustomAttributes(typeof(ParserAttribute), true);
if (attributes != null && attributes.Length > 0)
{
ParserAttribute parserAttribute = (ParserAttribute)attributes[0];
// create an instance of the identified parser
parser = (IParser<T>)Activator.CreateInstance(parserAttribute.ParserType);
// and add it to the dictionary
this._Parsers.Add(targetType, parser);
}
else
{
throw new InvalidOperationException(string.Format("Unable to find a parser for the type [{0}].", targetType.Name));
}
}
return (IParser<T>)parser;
}
// this method can be used to set up parsers without the use of the Parser attribute
public void RegisterParser<T>(IParser<T> parser)
{
this._Parsers[typeof(T)] = parser;
}
}
Exemple d'utilisation:
ParserRepository parserRepository = new ParserRepository();
// ...
IParser<MyObjectA> parserForMyObjectA = parserRepository.GetParser<MyObjectA>();
IParser<MyObjectB> parserForMyObjectB = parserRepository.GetParser<MyObjectB>();
using (var fs = new FileStream(@"file.ext", FileMode.Open, FileAccess.Read, FileShare.Read))
{
BinaryReader br = new BinaryReader(fs);
MyObjectA objA = parserForMyObjectA.Read(br);
MyObjectB objB = parserForMyObjectB.Read(br);
// ...
}
// Notice that this code does not explicitly reference the MyObjectAParser or MyObjectBParser classes.
Grande réponse, je lance rarement dans les codeurs qui pensent de cette façon. – ChaosPandion
D'accord, réponse vraiment géniale en effet. Cela le rend clair pour moi. Je n'y ai jamais pensé de cette façon. Plus vite n'est en effet pas toujours la définition de la meilleure implémentation. –
Content de vous aider! J'ai trouvé cette façon d'analyser le problème très utile, et j'ai évité de tels problèmes de concurrence! –