Использование статического класса или объявленного - PullRequest
2 голосов
/ 03 ноября 2010

В настоящее время я пытаюсь прочитать некоторые двоичные данные с помощью 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();

Что было бы лучшей реализацией.

Ответы [ 3 ]

8 голосов
/ 03 ноября 2010

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

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

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

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

1 голос
/ 03 ноября 2010

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

Что касается производительности, если вы неоднократно создавали много новых экземпляров вашего класса 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.
1 голос
/ 03 ноября 2010

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

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

...