Пользовательский оператор преобразования из базового класса - программирование
Подтвердить что ты не робот

Пользовательский оператор преобразования из базового класса

Введение

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

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

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

Поэтому я:

Метод A

class Entity
{
    IntPtr Pointer;

    Entity(IntPtr pointer)
    {
        this.Pointer = pointer;
    }
}

class Body : Entity
{
    Body(IntPtr pointer) : base(pointer) { }

    explicit operator Body(Entity e)
    {
        return new Body(e.Pointer);
    }
}

Это заклинание является незаконным. (Обратите внимание, что я не стал писать аксессуры). Без него компилятор позволит мне сделать:

Метод B

(Body)myEntity
...

Однако во время выполнения я получаю исключение, говорящее, что это приведение невозможно.

Заключение

Поэтому здесь я нуждаюсь в пользовательском преобразовании из базового класса, а С# отказывает мне. Используя метод A, компилятор будет жаловаться, но код будет логически работать во время выполнения. Используя метод B, компилятор не будет жаловаться, но код, очевидно, не будет работать во время выполнения.

В этой ситуации мне кажется странным, что MSDN говорит мне, что мне не нужен этот оператор, и компилятор действует так, как будто это возможно неявно (метод B). Что я должен делать?

Я знаю, что могу использовать:

Решение A

class Body : Entity
{
    Body(IntPtr pointer) : base(pointer) { }

    static Body FromEntity(Entity e)
    {
        return new Body(e.Pointer);
    }
}

Решение B

class Body : Entity
{
    Body(IntPtr pointer) : base(pointer) { }

    Body(Entity e) : base(e.Pointer) { }
}

Решение C

class Entity
{
    IntPtr Pointer;

    Entity(IntPtr pointer)
    {
        this.Pointer = pointer;
    }

    Body ToBody()
    {
        return new Body(this.Pointer);
    }
}

Но, честно говоря, все синтаксисы для них ужасны и на самом деле должны быть отличными. Итак, любой способ заставить броски работать? Это недостаток дизайна С# или я пропустил возможность? Это как если бы С# не доверял мне достаточно, чтобы написать собственное преобразование от базового к ребенку, используя свою систему литья.

4b9b3361

Ответ 1

Это не дизайнерский недостаток. Вот почему:

Entity entity = new Body();
Body body = (Body) entity;

Если вам было разрешено написать свое собственное пользовательское преобразование здесь, было бы два допустимых преобразования: попытка просто выполнить нормальный листинг (который является ссылочным преобразованием, сохраняющим личность) и вашим пользовательским преобразованием.

Что следует использовать? Вы действительно хотите, чтобы они делали разные вещи?

// Reference conversion: preserves identity
Object entity = new Body();
Body body = (Body) entity;

// User-defined conversion: creates new instance
Entity entity = new Body();
Body body = (Body) entity;

Юк! Таким образом, безумие лежит, ИМО. Не забывайте, что компилятор решает это во время компиляции, основываясь только на типах времени компиляции используемых выражений.

Лично я бы пошел с решением C - и, возможно, даже сделал его виртуальным методом. Таким образом, Body может переопределить его, чтобы просто вернуть this, если вы хотите, чтобы он сохранялся при сохранении идентичности, но при необходимости создавал новый объект.

Ответ 2

Ну, когда вы отбрасываете Entity в Body, вы не выполняете действительно кастинг друг к другу, а скорее добавляете IntPtr в новый объект.

Почему бы не создать явный оператор преобразования из IntPtr?

public class Entity {
    public IntPtr Pointer;

    public Entity(IntPtr pointer) {
        this.Pointer = pointer;
    }
}

public class Body : Entity {
    Body(IntPtr pointer) : base(pointer) { }

    public static explicit operator Body(IntPtr ptr) {
        return new Body(ptr);
    }

    public static void Test() {
        Entity e = new Entity(new IntPtr());
        Body body = (Body)e.Pointer;
    }
}

Ответ 3

Вы должны использовать свое решение B (аргумент конструктора); во-первых, почему бы не использовать другие предлагаемые решения:

  • Решение A является просто оболочкой для решения B;
  • Решение C просто неверно (почему базовый класс должен знать, как преобразовать себя в любой подкласс?)

Кроме того, если класс Body должен содержать дополнительные свойства, что они должны быть инициализированы при выполнении вашего броска? Это гораздо лучше использовать конструктор и инициализировать свойства подкласса, как это принято в языках OO.

Ответ 4

Причина, по которой вы не можете этого сделать, заключается в том, что она небезопасна в общем случае. Рассмотрим возможности. Если вы хотите это сделать, потому что базовый и производный классы взаимозаменяемы, у вас действительно есть только один класс, и вы должны объединить эти два. Если вы хотите, чтобы ваш оператор литья был удобен для использования базы downcast для вывода, вы должны учитывать, что не каждая переменная, введенная в качестве базового класса, укажет на экземпляр определенного производного класса, который вы пытаетесь отбросить к. Возможно, это так, но сначала вам нужно будет проверить или рискнуть недействительным литым исключением. То, почему downcasting, как правило, неодобрительно, и это не что иное, как понижение сопротивления. Я предлагаю вам пересмотреть свой дизайн.

Ответ 5

Как насчет:

public class Entity {...}

public class Body : Entity
{
  public Body(Entity sourceEntity) { this.Pointer = sourceEntity.Pointer; }
}

поэтому в коде вам не нужно писать:

Body someBody = new Body(previouslyUnknownEntity.Pointer);

но вы можете использовать

Body someBody = new Body(previouslyUnknownEntity);

вместо.

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

Примечание: не использовали компилятор, поэтому существует возможность опечатки.

Ответ 6

(Вызов протоколов некромантии...)

Здесь мой прецедент:

class ParseResult
{
    public static ParseResult Error(string message);
    public static ParseResult<T> Parsed<T>(T value);

    public bool IsError { get; }
    public string ErrorMessage { get; }
    public IEnumerable<string> WarningMessages { get; }

    public void AddWarning(string message);
}

class ParseResult<T> : ParseResult
{
    public static implicit operator ParseResult<T>(ParseResult result); // Fails
    public T Value { get; }
}

...

ParseResult<SomeBigLongTypeName> ParseSomeBigLongTypeName()
{
    if (SomethingIsBad)
        return ParseResult.Error("something is bad");
    return ParseResult.Parsed(new SomeBigLongTypeName());
}

Здесь Parsed() может вывести T из этого параметра, но Error не может, но он может вернуть безличный ParseResult, который можно конвертировать в ParseResult<T> - или это было бы, если бы не эта ошибка, Исправление заключается в возврате и преобразовании из подтипа:

class ParseResult
{
    public static ErrorParseResult Error(string message);
    ...
}

class ErrorParseResult : ParseResult {}

class ParseResult<T>
{
    public static implicit operator ParseResult<T>(ErrorParseResult result);
    ...
}

И все счастливо!