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


введение

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

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

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

8 ответов:

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

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

Ну, когда вы кастинг Entity to 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;
    }
}

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

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

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

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

как насчет:

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

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

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

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

вот мой прецедент:

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 не может, но он может вернуть typeless 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);
    ...
}

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

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

  • код

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

Ну, вот и все.

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

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

    public class b
        : a<b>
    {
    }