C #: Тип конкретных / статических методов, поддерживающих переопределение - PullRequest
1 голос
/ 16 сентября 2010

В C # невозможно переопределить статический метод.Тем не менее, у меня есть некоторые специфичные для типа методы (вместо обычных методов экземпляра), и я хочу дать пользователю возможность переопределять эти методы и, кроме того, иметь возможность вызывать эти (переопределенные) методы из моего собственного кода, не зная о«переопределение», сделанное пользователем.Если бы метод был статическим, мне нужно было бы заранее знать имя типа класса пользователя, а это не то, что мне нужно.

Как реализовать методы, специфичные для типа, такие как Read, Write и GetLength и разрешить переопределение?

Фон

Существует абстрактный класс Row, чьи производные типы / классы представляют тип строки ичей экземпляр представляет фактическую строку и ее поля / данные в очень простой таблице.Каждый тип строки имеет фиксированную длину, и таблица - это просто ряд строк в файле.Классу Row нужны три метода: методы Read и Write, которые выполняют свою очевидную функцию в потоке с заданным смещением, и метод GetLength, который возвращает фиксированную длину типа строки.

Теперь пользователь может расширить мой класс Row и предоставить реализации для Read, Write и GetLength для его или ее определенного типа строки , а также полей и свойств, которые будут использоваться в егоили ее конкретный экземпляр строки .Например, класс SomeUserRow может иметь 32-разрядное целочисленное поле и однобайтовое поле, фиксированную длину 5 байтов и соответствующую реализацию метода чтения и записи.

Методы

Прочитайте

Очевидный фабричный метод, связанный с типом, и поэтому я бы определил его в самом классе.Я бы сделал это static, но тогда это нельзя переопределить.Тогда я мог бы сделать это protected abstract и создать статический обобщенный метод Read<T> для его вызова, как предложено в этого поста .Но мне также нужно иметь возможность вызывать этот метод из моего кода, не зная типа реализованного пользователем класса.Я не могу просто позвонить Row.Read<UserType>(), потому что я еще не знаю о типах пользователя.

Запись

Возможный метод экземпляра, потому что большинство людей хотят написать существующий Rowв поток.Но имея Read static, кажется странным делать Write метод экземпляра.

GetLength

Не фабричный метод, но все же связанный с типом.Опять же, я бы сделал это статичным, но это предотвращает переопределение.Я могу выбрать метод экземпляра, который можно переопределить, но это неправильно делать в объектно-ориентированной среде: создание экземпляра просто для получения значения, которое не зависит от экземпляра (int length = new T().GetLength()) а точнее по его типу.

Я также думал о переносе трех методов из классов в отдельный класс, но это все еще не решает проблему переопределения.Или иметь класс, который хранит список или словарь делегатов, указывающих на правильные методы.Он не допускает переопределения real (замена указателя метода в массиве делегатов - это не то, что я бы рассмотрел true overriding), и он перемещает специфичные для типа методы от типа, которыйЯ думаю, что это нехорошо с точки зрения разработчика: иметь два или более мест для изменения, когда вы просто хотите изменить тип.

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

Ответы [ 4 ]

2 голосов
/ 16 сентября 2010

Я думаю, вы должны создать интерфейс IRowReader с методом Read. Тогда вы можете иметь несколько реализаций этого, которые возвращают Row (или, если необходимо, подкласс, который может переопределить Write или GetLength).

Другими словами, вам нужен тип фабрики, который содержит метод, который нужно переопределить. Как вы сказали, вы не можете переопределить статический метод в C #.

Тогда, конечно, вопрос в том, как получить правильный экземпляр IRowReader? Это зависит от приложения, но ваш клиент может инициализировать его при запуске или в какой-то другой четко определенной точке приложения; или вы можете использовать Внедрение зависимостей для его внедрения и настройки.

0 голосов
/ 16 сентября 2010

Одна из возможных стратегий - использовать статический метод, который вы обнаружите с помощью Reflection. К счастью, вам нужно всего лишь один раз написать код Reflection.

Предположим, у вас есть иерархия со статическими Read и GetLength методами:

public class Row {
    public static Row Read(Stream stream) { /* read a base row */ }
    public static int GetLength() { return /* length of base row */ }
}
public class DerivedRow : Row {
    public static DerivedRow Read(Stream stream) { /* read a derived row */ }
    public static int GetLength() { return /* length of derived row */ }
}

Теперь вы можете написать служебную функцию, которая будет читать строки любого типа:

public static class RowUtils
{
    public static Row Read<T>(Stream stream) where T : Row
    {
        var method = typeof(T).GetMethod("Read",
            BindingFlags.Static | BindingFlags.Public);
        if (method == null || !typeof(Row).IsAssignableFrom(method.ReturnType))
            throw new InvalidOperationException(string.Format(
                "Static Read method on type “{0}” does not exist or " +
                "has the wrong return type.", typeof(T).FullName));

        return (Row) method.Invoke(null, new object[] { stream });
    }
}

То же самое, конечно, для GetLength(). Затем вы можете написать методы, которые принимают тип строки в качестве параметра типа, например

public IEnumerable<Row> ReadRows<TRow>(Stream stream)
    where TRow : Row
{
    var numRows = stream.Length / RowUtils.GetLength<TRow>();
    for (long i = 0; i < numRows; i++)
        yield return RowUtils.Read<TRow>(stream);
}

// Example call...
var rowsInFile = ReadRows<DerivedRow>(File.Open(...));
0 голосов
/ 16 сентября 2010

Я думаю, абстрактный базовый класс (RowBase) и фабричные классы решат эту проблему. Вы можете определить, какой тип производного класса вас интересует, используя файл конфигурации или что-то еще. В основном вы здесь используете шаблон стратегии.

0 голосов
/ 16 сентября 2010

Я тоже столкнулся с этой проблемой, и разочаровывает, что нет «чистого» решения.Вот обходной путь, который я использовал, возможно, он вам понравится.

Предположим, у вас есть иерархия с Row в качестве базового класса.

public class Row { }
public class DerivedRow : Row { }

Создайте другую иерархию, параллельную этомуодин, но основывать его на абстрактном базовом классе под названием Factory.

public abstract class Factory {
    public abstract Row Read(Stream stream);
    public abstract int GetLength();
}
public class RowFactory : Factory {
    public override Row Read(Stream stream) { /* read a base row */ }
    public override int GetLength() { return /* length of base row */ }
}
public class DerivedRowFactory : Factory {
    public override Row Read(Stream stream) { /* read a derived row */ }
    public override int GetLength() { return /* length of derived row */ }
}

Теперь вы можете написать методы, которые принимают фабрику в качестве параметра типа, например

public IEnumerable<Row> ReadRows<TRowFactory>(Stream stream)
    where TRowFactory : Factory, new()
{
    var factory = new TRowFactory();
    var numRows = stream.Length / factory.GetLength();
    for (long i = 0; i < numRows; i++)
        yield return factory.Read(stream);
}

// Example call...
var rowsInFile = ReadRows<DerivedRowFactory>(File.Open(...));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...