C # mimic переопределяет оператор присваивания (=) - PullRequest
5 голосов
/ 14 марта 2009

У меня небольшая проблема с довольно простым классом-оберткой, который у меня есть.

Это выглядит примерно так:

public class Wrapper<T>
{
  private T _value;

  public Wrapper<T>(T value)
  {
    _value = value;
  }

  public static implicit operator Wrapper<T>(T value)
  {
    return new Wrapper<T>(value);
  }

  public static implicit operator T(Wrapper<T> value)
  {
    return value._value;
  }
}

Я переопределил неявные преобразователи из и в T, поэтому он ведет себя почти как экземпляр самого T.

например.

Wrapper<int> foo = 42;

Однако у меня возникла небольшая проблема при назначении одного экземпляра Wrapper другому, поскольку я хочу назначить только значение второго класса Wrapper.

Так что сейчас я должен сделать это:

Wrapper<int> foo = 42;
Wrapper<int> bar = (int)foo;

Или публично раскрыть _value через свойство.

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

Проблема простого изменения указателя (как это происходит при назначении экземпляра класса другому) заключается в том, что у меня есть словарь указателей на эти объекты Wrapper, поэтому я не могу их постоянно менять, так как тогда словарь перестанет совпадать.

Я вижу, что это несколько сбивает с толку, поэтому, если я пропустил что-то важное, пожалуйста, не стесняйтесь спрашивать: -)

Ответы [ 8 ]

5 голосов
/ 14 марта 2009

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

Один из вариантов - перегрузить конструктор:

public Wrapper(Wrapper<T> w)
{
    _value = w._value;
}

Что приведет к следующему синтаксису:

Wrapper<int> foo = 42;
Wrapper<int> bar = new Wrapper<int>(foo);

Хотя это более многословно, чем то, что у вас есть, оно читается лучше.

Или вы можете добавить метод Clone (не интерфейс ICloneable), чтобы вы могли написать:

Wrapper<int> bar = foo.Clone();

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

3 голосов
/ 14 марта 2009

Вы можете сделать Wrapper a struct. Однако я не уверен, подойдет ли это вашему дизайну приложения или нет.

1 голос
/ 14 марта 2009

Не передавайте свою обертку неявно в обоих направлениях.

public class DBValue<T>
{
    public static implicit operator DBValue <T>(T value)
    {
         return new DBValue<T>(value);
    }

    public static explicit operator T(DBValue <T> dbValue)
    {
         return dbValue.Value;
    }

    private readonly T _value;
    public T Value { get { this._value; } }

    public DBValue(T value)
    {
         this._value = value;
    }
}

Преобразование из DBValue<T> в T - это преобразование с потерями (как минимум, вы теряете тот факт, что это значение из базы данных), и в соответствии с передовым опытом должно быть явным. Если вы ничего не потеряете, переведя с DBValue<T> на T, вы можете просто использовать свойства, которые возвращают T.

По сути, вы уже поняли, почему вам не следует пытаться сделать это: если DBValue можно заменить на T и наоборот, как компилятор (или разработчик) узнает, какой из них выбрать?

Требуется, чтобы разработчики из нисходящего потока написали:

string value = MyProperty.Value

или

string value = (string)MyProperty

вместо

string value = MyProperty

... не все так обременительно и гарантирует, что все точно знают, что происходит.

EDIT:

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

1 голос
/ 14 марта 2009

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

Проблема простого изменения указателя (как это происходит при назначении экземпляра класса другому) заключается в том, что у меня есть словарь указателей на эти объекты Wrapper, поэтому я не могу их постоянно менять, поскольку тогда словарь перестанет совпадать.

Я не уверен, что следую этому, что именно вы храните в словаре? Потому что, если вы храните ссылки, CLR будет обновлять их по мере необходимости.

0 голосов
/ 09 сентября 2014

Для этого старого поста нужна дополнительная информация. Очевидно, что первоначальное желаемое поведение не может быть достигнуто, поскольку оператор = не может быть перегружен, и аналогично C # не может быть «обманут» при приведении объекта к его собственному типу ... он всегда сводится к присвоению ссылки на класс. Но дальнейшие публикации Штеффена показывают, что класс Wrapper используется не только с локальными переменными, но и как тип поля класса . Можно использовать желаемую семантику И поддерживать целостность внутреннего словаря, используя свойства класса вместо открытых полей.


Даже сохраняя исходный заданный класс Wrapper<T> с обоими его неявными операторами, вот код, который будет работать:

public class CustomerTable
{
    private Wrapper<string> _Name;
    public Wrapper<string> Name {
        get { return _Name; }
        set { _Name = (string)value; }
    }
}

public class UserTable
{
    private Wrapper<string> _Name;
    public Wrapper<string> Name {
        get { return _Name; }
        set { _Name = (string)value; }
    }
}

Если бы это изменение было сделано, оно не нарушило бы существующий код, поскольку оно все еще допускает различные режимы установки свойства:

CustomerTable customer = new CustomerTable();
UserTable user = new UserTable();

user.Name = customer.Name; //*** No longer breaks internal data structures

user.Name = "string literal";  // Works as expected with implicit cast operator
user.Name = (string)customer.Name; // Still allowed with explicit/implicit cast operator
user.Name = customer.Name.Value; // Also works if Value property is still defined

Поскольку это по-прежнему не отвечает первоначальному вопросу, использование класса Wrapper все еще может быть проблематичным, если его использовать вне контекста свойства класса, т. Е. Передавать между объектом и т. Д. Возможно, весь класс Wrapper можно было бы устранить с помощью надлежащего дизайн класса, включая использование свойства set / get accessors.

0 голосов
/ 14 марта 2009

A J Lane Я понимаю, что вы имеете в виду, и, наверное, вы правы - я просто хотел максимально упростить использование библиотеки.

Причиной неявного приведения из DbValue в T является просто функция, которая ожидает T.

например

literalSomething.Text = Server.HtmlEncode(SomeTable.SomeStringColumn);

вместо

literalSomething.Text = Server.HtmlEncode((string)SomeTable.SomeStringColumn);

Для этого требуется, чтобы приведение было неявным.

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

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

Представьте себе DbValue:

if (someValue.Value.HasValue) // someValue is DbValue<int?>

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

Полагаю, этот вопрос на самом деле становится вопросом "наилучшей практики".

Итак, в заключение я создам свойство Value и использую его вместо неявных приведений, и разработчику, использующему библиотеку, просто придется с этим смириться.

Спасибо за ваш вклад: -)

0 голосов
/ 14 марта 2009

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

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

Я могу опубликовать весь класс, если это будет полезно, но это несколько длинно (130 строк) Или я мог бы бросить на отдельном сервере, если бы это было лучше? (хотя это вредит целостности этого вопроса, поскольку я могу в конечном итоге удалить его с этого сервера)

Также очень сложно объяснить класс без написания полного эссе: - /

В любом случае, я попытаюсь проиллюстрировать мою проблему.

Предположим, 2 класса таблиц: CustomerTable и UserTable:

public class CustomerTable
{
  Wrapper<string> Name;
}

public class UserTable
{
  Wrapper<string> Name;
}

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

CustomerTable customer = new CustomerTable();
UserTable user = new UserTable();
user.Name = customer.Name; // This breaks my internal dictionary

Что должен был сделать разработчик, чтобы он работал, было:

user.Name = (string)customer.Name;

Однако проблема в том, кто в здравом уме подумает об этом при написании кода?

Даже если бы я использовал свойство Value, разработчику все равно пришлось бы не забывать писать

user.Name = customer.Name.Value; // or user.Name.Value = ....

И снова разработчик может забыть об этом, и внезапно он получает исключения или, что еще хуже: данные, которые не сохраняются в базе данных.

Так что моя проблема в том, что я хочу, чтобы обертка была полностью прозрачной (она должна использоваться, как если бы она была на самом деле классом / примитивом, который она оборачивает). Однако при назначении из одной оболочки другой внутренняя логика ломается.

Фух много написания и много кода - дайте мне знать, если я переусердствовал написание.

0 голосов
/ 14 марта 2009

Это то, что свойства для. Они позволяют вам определить, что означает назначение. Вы не можете определить это для класса или самой структуры, потому что они уже определены языком для выполнения необходимых действий. Просто добавьте свойство Value в класс.

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

...