2010-11-02 5 views
2

В настоящее время я пытаюсь прочитать некоторые двоичные данные с помощью BinaryReader. Я создал вспомогательный класс для анализа этих данных. В настоящее время это статический класс с такого рода методов:Использование статического класса или объявленного

public static class Parser 
{ 
    public static ParseObject1 ReadObject1(BinaryReader reader){...} 
    public static ParseObject2 ReadObject2(BinaryReader reader{...} 
} 

Затем я использую это так:

... 
BinaryReader br = new BinaryReader(@"file.ext"); 
ParseObject1 po1 = Parser.ReadObject1(br); 
... 
ParseObject1 po2 = Parser.ReadObject2(br); 
... 

Но тогда я начал думать, я мог бы также просто инициализировать класс, как этот

Parser p = new Parser(br); 
ParseObject1 po1 = Parser.ReadObject1(); 

Что было бы лучше.

ответ

8

Который более быстрый на самом деле не очень уместен здесь; ваши проблемы больше связаны с параллелизмом и архитектурой.

В случае статического класса Parser, которому вы передаете BinaryReader в качестве аргумента для вызова ReadObject, вы предоставляете все данные методу и (предположительно, из вашего примера), не сохраняя никаких данных о читателе в парсере; это позволяет вам создавать экземпляры нескольких объектов BinaryReader и вызывать Parser отдельно, без проблем параллелизма или столкновения. (Обратите внимание, что это ТОЛЬКО применяется, если у вас нет постоянных статических данных в объекте Parser.)

С другой стороны, если ваш Parser получает объект BinaryReader для работы, он, по-видимому, сохраняет данные BinaryReader внутри себя; там есть возможное усложнение, если у вас есть чередующиеся вызовы на ваш Parser с разными объектами BinaryReader.

Если вашему Parser не требуется поддерживать состояние между ReadObject1 и ReadObject2, я бы рекомендовал сохранить его статическим и передать в ссылке объект BinaryReader; сохраняя его статическим в этом случае, является хорошим «дескриптором» того факта, что между этими вызовами не сохраняются данные. С другой стороны, если в Parser сохраняются данные о BinaryReader, я бы сделал его нестатичным и передал данные (как в вашем втором примере). Если сделать это нестатистическим, но с данными с сохранением класса, это делает менее вероятным возникновение проблем с параллелизмом.

+3

Великий ответ, я редко сталкиваются с кодеров, которые думают таким образом. – ChaosPandion

+2

Хорошо, действительно потрясающий ответ. Это дает мне понять. Никогда не думал об этом таким образом. Быстрее - это не всегда определение лучшей реализации. –

+0

Рад помочь! Я нашел, что этот способ анализа проблемы очень полезен, и я избегал неприятных проблем параллелизма таким образом! –

1

Существует, вероятно, незначительная разница в производительности между двумя реализациями. Я ожидаю, что чтение двоичного файла займет> 99% времени выполнения.

Если вы действительно заинтересованы в производительности, вы можете обернуть обе реализации в отдельных циклах и времени их.

1

Разница в производительности между этими двумя подходами должна быть незначительной. Лично я бы предложил использовать нестатический подход из-за гибкости, которую он предоставляет. Если вы считаете полезным использовать большую часть логики синтаксического анализа в одном месте, вы можете использовать комбинированный подход (продемонстрированный в моем примере ниже).

Что касается производительности, если вы неоднократно создавали много новых экземпляров своего класса Parser за короткий промежуток времени, вы можете заметить небольшое влияние на производительность, но тогда вы, вероятно, сможете реорганизовать код, чтобы избежать повторного создания экземпляров класса Парсера. Кроме того, при вызове метода экземпляра (особенно виртуального метода) технически не так быстро, как вызов статического метода, опять же разница в производительности должна быть очень незначительной.

McWafflestix поднимает хороший момент о состоянии.Однако, учитывая, что ваша текущая реализация использует статические методы, я предполагаю, что вашему классу Parser не нужно поддерживать состояние между вызовами методов Read, и поэтому вы должны иметь возможность повторно использовать один и тот же экземпляр Parser для анализа нескольких объектов из BinaryReader поток.

Ниже приведен пример, который иллюстрирует подход, который я, вероятно, возьму на эту проблему. Вот некоторые особенности этого примера:

  • Использование полиморфизма для абстрактных подробностей о том, где находится логика синтаксического анализа для данного типа объекта.
  • Использование хранилища для хранения экземпляров Parser, чтобы их можно было повторно использовать.
  • Использование отражения для идентификации логики синтаксического анализа для данного класса или структуры.

Обратите внимание, что я сохранил логику синтаксического анализа в статических методах в ParseHelper класса, а Read методы экземпляра на MyObjectAParser и MyObjectBParser классов используют эти статические методы на ParseHelper класса. Это просто дизайнерское решение, которое вы можете сделать в зависимости от того, что для вас наиболее важно, как организовать логику синтаксического анализа. Я предполагаю, что, вероятно, имеет смысл переместить некоторую логику синтаксического анализа, специфичную для конкретного типа, в отдельные классы Parser, но сохранить часть общей логики синтаксического анализа в классе 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; 
    } 
} 

Пример использования:

 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. 
+0

Wow xD это довольно хорошее гибкое решение. Однако, похоже, что у меня есть некоторые накладные расходы для моего текущего варианта использования. Я пашу, буду поддерживать это, потому что он действительно узнал меня и дал мне другое представление о том, как можно устранить эти проблемы. –

Смежные вопросы