Programing

기본 클래스의 사용자 정의 변환 연산자

crosscheck 2020. 11. 21. 15:40
반응형

기본 클래스의 사용자 정의 변환 연산자


소개

"기본 클래스에 대한 사용자 정의 변환이 허용되지 않음"을 알고 있습니다. 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 #이 캐스트 시스템을 사용하여 내 자신의 기본-자식 변환을 작성할만큼 나를 신뢰하지 않은 것 같습니다.


디자인 결함이 아닙니다. 그 이유는 다음과 같습니다.

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

여기에 사용자 정의 변환을 작성할 수있는 경우 두 가지 유효한 변환이 있습니다. 하나는 일반 캐스트 (참조 변환, ID 유지)를 수행하려는 시도와 사용자 정의 변환입니다.

어느 것을 사용해야합니까? 당신이겠습니까 정말 원하는이 다른 일을 할 것입니다 그래서?

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

수다! 그런 식으로 광기가 놓여 있습니다, IMO. 컴파일러는 관련 표현식 컴파일 타임 유형만을 기반으로하여 컴파일 타임에 이것을 결정한다는 것을 잊지 마십시오 .

개인적으로 저는 솔루션 C를 사용하고 가상 방법으로 만들 수도 있습니다. 가능한 경우 ID를 보존 하고 필요한 경우 새 객체를 생성 하려는 경우이 방법 Body 을 사용 하여을 반환하도록 재정의 할 있습니다 .this


당신이 캐스팅 할 때 음, EntityBody, 당신은되지 않습니다 정말 다른 하나를 주조, 오히려 캐스팅 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 언어의 관례대로 생성자를 사용하고 서브 클래스의 속성을 초기화하는 것이 훨씬 좋습니다.


할 수없는 이유는 일반적인 경우에 안전하지 않기 때문입니다. 가능성을 고려하십시오. 기본 클래스와 파생 클래스가 상호 교환 가능하기 때문에이 작업을 수행하려면 실제로 하나의 클래스 만 있고 두 클래스를 병합해야합니다. 기본을 파생으로 다운 캐스트 할 수있는 편의를 위해 캐스트 연산자를 사용하려면 기본 클래스로 입력 된 모든 변수가 캐스트하려는 특정 파생 클래스의 인스턴스를 가리키는 것은 아니라는 점을 고려해야합니다. 에. 그것은 수있는 그런 식으로 될 수 있지만 먼저 확인하거나 잘못된 캐스트 예외 위험을 감수해야합니다. 그렇기 때문에 다운 캐스팅이 일반적으로 눈살을 찌푸리는 이유이며 이것은 드래그로 다운 캐스팅에 지나지 않습니다. 나는 당신의 디자인을 다시 생각할 것을 제안합니다.


어때 :

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할 수는 없지만 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);
    ...
}

그리고 모든 것이 행복합니다!


It seems the reference equality was not your concern, then you can say:

  • Code

    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) {
        }
    }
    

and the

  • Test

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

You didn't write the accessors but I took the encapsulation into account, to not expose their pointers. Under the hood of this implementation is use an intermediate class which is not in the inheritance chain but chain the conversion.

Activator involved here is good for not adding extra new() constraint as U are already constrained to Entity and have a parameterized constructor. To<U> though is exposed but sealed without exposing its constructor, it can only be instantiated from the conversion operator.

In the test code, the entity actually converted to a generic To<U> object and then the target type, so is the extra demonstration from body to context. Because To<U> is a nested class, it can access the private Pointer of the containing class, thus we can accomplish things without exposing the pointer.

Well, that's it.


you can use generic, its possible like blow

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

    public class b
        : a<b>
    {
    }

참고URL : https://stackoverflow.com/questions/3401084/user-defined-conversion-operator-from-base-class

반응형