Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Как жить без const?

Anna | 17.06.2014 | нет комментариев
Часто, передавая объект в какой-либо способ, нам бы хотелось сказать ему: «Вот, держи данный объект, но ты не имеешь право изменять его», и как-то подметить это при вызове. Плюсы очевидны: помимо того, что код становится надёжнее, он становится ещё и больше читаемым. Нам не необходимо заходить в реализацию всякого способа, Дабы отследить, как и где изменяется волнующий нас объект. Больше того, если константность передаваемых доводов указана в сигнатуре способа, то по самой такой сигнатуре, с той либо другой точностью, теснее дозволено предположить, что же он собственно делает. Ещё один плюс – потокобезопасность, т.к. мы знаем, что объект является read only.
В C/C для этих целей существует ключевое слово const. Многие скажут, что такой механизм слишком ненадёжен, впрочем, в C# нет и такого. И допустимо он появится в грядущих версиях (разработчики этого не опровергают), но как же быть теперь?

1. Неизменяемые объекты (Immutable objects)

Самый знаменитый сходственный объект в C# — это строка (string). В нём нет ни одного способа, приводящего к изменению самого объекта, а только к созданию нового. И всё с ними как бы бы отлично и прекрасно (они примитивны в применении и надёжны), пока мы не припомним о продуктивности. К примеру, обнаружить подстроку дозволено и без копирования каждого массива символов, впрочем, что если нам необходимо, скажем, заменить символы в строке? А что если нам необходимо обработать массив из тысяч таких строк? В всяком случае будет производиться создание нового объекта строки и копирование каждого массива. Ветхие строки нам теснее не необходимы, но сами строки ничего не знают об этом и продолжают копировать данные. Только разработчик, вызывая способ, может давать либо не давать право на метаморфоза объектов-доводов, но как это сделать?

2. Интерфейс

Один из вариантов – сделать для объекта read only интерфейс, из которого исключить все способы, изменяющие объект. А если данный объект является generic’ом, то к интерфейсу дозволено добавить ещё и ковариантность. На примере с вектором это будет выглядеть так:

interface IVectorConst<out T>
{
    T this[int nIndex] { get; }
}

class Vector<T> : IVectorConst<T>
{
    private readonly T[] _vector;

    public Vector(int nSize)
    {
        _vector = new T[nSize];
    }

    public T this[int nIndex]
    {
        get { return _vector[nIndex]; }
        set { _vector[nIndex] = value; }
    }
}

void ReadVector(IVectorConst<int> vector)
{
   ...
}

(Кстати, между Vector и IVectorConst (либо IVectorReader – кому как нравится) дозволено добавить ещё и контравариантный IVectorWriter.)

И всё бы ничего, но ReadVector’у ничто не мешает сделать downcast к Vector и изменить его. Впрочем, если припомнить const из C , данный метод ничем не менее надёжен, как столь же ненадёжный const, никак не воспрещающий всякие реформирования указателей. Если вам этого довольно, дозволено остановиться, если нет – идём дальше.

3. Отделение константного объекта

Запретить вышеупомянутый downcast мы можем только одним методом: сделать так, Дабы Vector не наследовал от IVectorConst, то есть отделить его. На том же примере с вектором, это будет выглядеть дальнейшим образом:

struct VectorConst<T>
{
    private readonly T[] _vector;

    public VectorConst(T[] vector)
    {
        _vector = vector;
    }

    public T this[int nIndex]
    {
        get { return _vector[nIndex]; }
    }
}

struct Vector<T>
{
    private readonly T[] _vector;
    private readonly VectorConst<T> _reader;

    public Vector(int nSize)
    {
        _reader = new VectorConst<T>(_vector = new T[nSize]);
    }

    public T this[int nIndex]
    {
        set { _vector[nIndex] = value; }
    }

    public VectorConst<T> Reader
    {
        get { return _reader; }
    }
}

Сейчас наш VectorConst отделён и, отдавая его кому-то, мы можем спать спокойно, будучи уверенными, что наш вектор останется в постоянном виде. Всё, чем нам пришлось за это уплатить, — это инициализация конструкции VectorConst копированием ссылки на _vector и добавочная ссылка в памяти. При передаче VectorConst в способ происходит вызов свойства и такое же копирование. Таким образом, дозволено сказать, что по продуктивности это фактически равносильно передаче в способ экземпляра T[], но с охраной от изменений. А Дабы не вызывать очевидно ненужный раз качество Reader, дозволено добавить в Vector оператор реформирования:

public static implicit operator VectorConst<T>(Vector<T> vector)
{
    return vector._reader;
}
4. Вариативность

И вновь есть одно «но»: наши конструкции не вариативны. Для этого отнаследуем их от IVectorConst и IVector. Но тут появляется один нюанс… Дабы не пришлось позднее править код, было бы хорошо лишить разработчика вероятности указывать в доводах способов VectorConst, а только IVectorConst. Для этого дозволено спрятать конструкцию VectorConst внутри Vector, объявив её как private. Но при этом мы теряем в продуктивности: доступ к структуре через интерфейс отнимает значительно огромнее времени. И даже сделав её классом, мы немножко выиграем, устранив распаковку, но доступ через интерфейс всё равно будет дольше прямого обращения к классу. К тому же, C# воспрещает создание операторов реформирования к интерфейсам, следственно мы лишились ещё и «прекрасного» вызова способов с VectorConst в качестве довода. Посему, воздержимся от этого (правда такой подход имеет право на жизнь). Выходит, вот что у нас в результате получилось:

interface IVectorConst<out T>
{
    T this[int nIndex] { get; }
}

interface IVector<in T>
{
    T this[int nIndex] { set; }
}

struct VectorConst<T> : IVectorConst<T>
{
    private readonly T[] _vector;

    public VectorConst(T[] vector)
    {
        _vector = vector;
    }

    public T this[int nIndex]
    {
        get { return _vector[nIndex]; }
    }
}

struct Vector<T> : IVector<T>
{
    private readonly T[] _vector;
    private readonly VectorConst<T> _reader;

    public Vector(int nSize)
    {
        _reader = new VectorConst<T>(_vector = new T[nSize]);
    }

    public T this[int nIndex]
    {
        set { _vector[nIndex] = value; }
    }

    public VectorConst<T> Reader
    {
        get { return _reader; }
    }

    public static implicit operator VectorConst<T>(Vector<T> vector)
    {
        return vector._reader;
    }
}

Стоит впрочем учесть, что если нам потребуется применять ковариантность IVectorConst, нам всё равно придётся вызывать качество Reader, невзирая на присутствие оператора реформирования:

class A
{
}

class B : A
{
}

private static void ReadVector(IVectorConst<A> vector)
{
    ...
}

var vector = new Vector<B>();
ReadVector(vector.Reader);

Многие наверно скажут, что всё это прописные истины. Но допустимо для кого-то эта статья и эти несложные образцы окажутся пригодными. Если у кого есть ещё какие-то идеи, касаемо этой темы, буду рад комментариям.

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB