Пользовательский оператор преобразования из базового класса - PullRequest
64 голосов
/ 04 августа 2010

Введение

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

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

Заключение

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

Ответы [ 8 ]

41 голосов
/ 13 августа 2010

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

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

18 голосов
/ 10 августа 2010

Ну, когда вы разыгрываете 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;
    }
}
9 голосов
/ 04 августа 2010

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

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

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

2 голосов
/ 15 августа 2010

Как насчет:

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 голосов
/ 11 августа 2010

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

1 голос
/ 09 августа 2017

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

Вот мой пример использования:

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

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

0 голосов
/ 19 мая 2018

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

public class a<based>
    {
        public static implicit operator b(a<based> v)
        {
            return new b();
        }
    }

    public class b
        : a<b>
    {
    }
0 голосов
/ 07 января 2018

Кажется, референтное равенство не было вашей заботой, тогда вы можете сказать:

  • Код

    public class Entity {
        public sealed class To<U> where U : Entity {
            public static implicit operator To<U>(Entity entity) {
                return new To<U> { m_handle=entity.Pointer };
            }
    
            public static implicit operator U(To<U> x) {
                return (U)Activator.CreateInstance(typeof(U), x.m_handle);
            }
    
            To() { // not exposed
            }
    
            IntPtr m_handle; // not exposed
        }
    
        IntPtr Pointer; // not exposed
    
        public Entity(IntPtr pointer) {
            this.Pointer=pointer;
        }
    }
    

    public class Body:Entity {
        public Body(IntPtr pointer) : base(pointer) {
        }
    }
    
    // added for the extra demonstration
    public class Context:Body {
        public Context(IntPtr pointer) : base(pointer) {
        }
    }
    

и

  • Тест

    public static class TestClass {
        public static void TestMethod() {
            Entity entity = new Entity((IntPtr)0x1234);
            Body body = (Entity.To<Body>)entity;
            Context context = (Body.To<Context>)body;
        }
    }
    

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

Activator подходит для того, чтобы не добавлять дополнительные ограничения new(), поскольку U уже ограничены Entity и имеют параметризованный конструктор.To<U> хотя и выставляется, но запечатывается без показа своего конструктора, его можно создать только из оператора преобразования.

В тестовом коде сущность фактически преобразуется в универсальный объект To<U>, а затем в целевой тип, как и дополнительная демонстрация от body до context.Поскольку To<U> является вложенным классом, он может получить доступ к закрытому Pointer содержащего класса, таким образом, мы можем выполнять задачи без раскрытия указателя.

Ну вот и все.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...