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

Введение

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

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

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

Я поэтому имею:

Метод 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
...

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

Заключение

Поэтому здесь я, нуждаясь в пользовательском преобразовании из базового класса, и C# отказывается от него мне. Используя метод 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);
    }
}

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

64
задан John Saunders 12 August 2010 в 23:04
поделиться

5 ответов

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

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 , если вы хотите, чтобы он сохранял идентичность , где это возможно , но создавал новый объект там, где это необходимо.

41
ответ дан 24 November 2019 в 16:00
поделиться

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

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

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

9
ответ дан 24 November 2019 в 16:00
поделиться

Ну, когда вы преобразовываете 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;
    }
}
18
ответ дан 24 November 2019 в 16:00
поделиться

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

2
ответ дан 24 November 2019 в 16:00
поделиться

Как насчет:

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);

.

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

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

2
ответ дан 24 November 2019 в 16:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: