Operator konwersji zdefiniowany przez użytkownika z klasy bazowej

Wprowadzenie

Jestem świadomy, że "konwersje definiowane przez użytkownika do lub z klasy bazowej nie są dozwolone". MSDN podaje, jako wyjaśnienie tej zasady, "nie potrzebujesz tego operatora."

Rozumiem, że zdefiniowana przez użytkownika konwersja do klasy bazowej nie jest potrzebna, ponieważ oczywiście odbywa się to pośrednio. Jednak potrzebuję konwersji z klasy bazowej.

W moim obecnym projekcie, opakowaniu niezarządzanego kodu, używam wskaźnika, przechowywanego w / Align = "left" / Wszystkie klasy używające wskaźnika wywodzą się z tej klasy encji, na przykład klasy ciała.

Dlatego mam:

Metoda 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);
    }
}
Ta obsada jest nielegalna. (Zauważ, że nie zawracałem sobie głowy pisaniem akcesoriów). Bez niego kompilator pozwoli mi zrobić:

Metoda B

(Body)myEntity
...

Jednakże, w czasie wykonywania, będę miał wyjątek mówiąc, że ta obsada jest niemożliwa.

Wniosek

Dlatego Oto jestem, potrzebuje konwersji zdefiniowanej przez Użytkownika z klasy bazowej, A C# odmawia mi jej. Używając metody A, kompilator będzie narzekał, ale kod będzie logicznie działał w czasie wykonywania. Używając metody B, kompilator nie będzie narzekał, ale kod oczywiście zawiedzie w czasie wykonywania.

Dziwne jest to, że MSDN mówi mi, że nie potrzebuję tego operatora, a kompilator zachowuje się tak, jakby było to możliwe bezwarunkowo (metoda B). Co mam zrobić? zrobić?

Zdaję sobie sprawę, że mogę użyć:

Rozwiązanie A

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

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

Roztwór B

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

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

Roztwór C

class Entity
{
    IntPtr Pointer;

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

    Body ToBody()
    {
        return new Body(this.Pointer);
    }
}
[[5]}ale szczerze mówiąc, wszystkie składnie dla nich są straszne i powinny w rzeczywistości być odlewy. Jak sprawić, by odlewy zadziałały? Czy to wada C# design czy przegapiłem taką możliwość? To tak, jakby C# nie ufało mi na tyle, aby napisać własną konwersję base-to-child za pomocą ich systemu cast.
 57
Author: John Saunders, 2010-08-04

8 answers

To nie jest wada projektowa. Oto dlaczego:

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

Gdybyś mógł napisać własną konwersję zdefiniowaną przez Użytkownika tutaj, byłyby dwie ważne konwersje: próba wykonania zwykłej konwersji (która jest konwersją referencyjną, zachowującą tożsamość) i konwersja zdefiniowana przez użytkownika.

Które należy stosować? Czy naprawdę chcesz, aby te robiły różne rzeczy?

// 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;
Yuk! W ten sposób szaleństwo leży, IMO. Nie zapominaj, że kompilator decyduje o tym w compile-time, bazując tylko na typach compile-time. Osobiście wybrałbym rozwiązanie C i być może nawet zrobił z niego metodę wirtualną. Tędy.Body może przesłaniać, aby po prostu zwrócić this, Jeśli chcesz, aby to było zachowywanie tożsamości , gdzie to możliwe , ale tworzenie nowego obiektu, gdy jest to konieczne.
 35
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-14 20:17:05

Kiedy rzucasz Entitydo Body, nie rzucasz naprawdęjednego do drugiego, ale raczej rzucasz IntPtr do nowego bytu.

Dlaczego nie utworzyć jawnego operatora konwersji z 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;
    }
}
 16
Author: Igor Zevaka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-10 01:13:53

Powinieneś użyć swojego rozwiązania B( argumentu konstruktora); po pierwsze, oto dlaczego nie używać innych proponowanych rozwiązań:

  • roztwór A jest jedynie opakowaniem dla roztworu B;
  • rozwiązanie C jest po prostu złe (dlaczego klasa bazowa powinna wiedzieć, jak przekonwertować się na dowolną podklasę?)

Ponadto, jeśli klasa Body ma zawierać dodatkowe właściwości, to do czego należy je zainicjować podczas wykonywania rzutu? O wiele lepiej jest użyć konstruktora i inicjalizuj właściwości podklasy tak, jak to jest w językach OO.

 9
Author: robyaw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-04 07:54:42

Powodem, dla którego nie możesz tego zrobić, jest to, że nie jest to bezpieczne w ogólnym przypadku. Zastanów się nad możliwościami. Jeśli chcesz być w stanie to zrobić, ponieważ klasa bazowa i pochodna są wymienne, to tak naprawdę masz tylko jedną klasę i powinieneś połączyć te dwie. Jeśli chcesz mieć swój operator cast dla wygody bycia w stanie downcast bazy do pochodnej, to musisz wziąć pod uwagę, że nie każda zmienna wpisana jako klasa bazowa będzie wskazywać na instancję określonej klasy pochodnej próbujesz go rzucić. To może BYĆ w ten sposób, ale musisz najpierw sprawdzić, lub zaryzykować nieważny wyjątek od obsady. Dlatego downcasting jest ogólnie mile widziany i to nic więcej niż downcasting w przeciąganiu. Proponuję przemyśleć swój projekt.

 2
Author: siride,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-10 20:43:21

A może:

public class Entity {...}

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

Więc w kodzie nie musisz pisać:

Body someBody = new Body(previouslyUnknownEntity.Pointer);

Ale możesz użyć

Body someBody = new Body(previouslyUnknownEntity);
Zamiast tego.

To tylkokosmetyczna zmiana , wiem, ale jest to dość jasne i można łatwo zmienić wewnętrzne. Jest również używany we wzorze owijania, którego nazwy nie pamiętam (dla lekko różniących się. celów).
Jest również jasne, że tworzysz nowy obiekt z dostarczonego, więc nie powinno to być mylące, ponieważ operator / konwersja be.

Uwaga: nie używałem kompilatora, więc istnieje możliwość literówki.

 1
Author: Jaroslav Jandek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-15 06:23:00

(powołując się na protokoły nekromancji...)

Oto mój przypadek użycia:

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

Tutaj {[2] } może wywnioskować T ze swojego parametru, ale Error nie może, ale może zwrócić typeless ParseResult, który jest zamieniany na ParseResult<T> - lub byłoby, gdyby nie ten błąd. Poprawką jest zwrócenie i konwersja z podtypu:

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

class ErrorParseResult : ParseResult {}

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

I wszystko jest szczęśliwe!

 1
Author: Simon Buchan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-09 04:15:32

Wydaje się, że równość odniesienia nie była twoim zmartwieniem, wtedy możesz powiedzieć:

  • Kod

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

I

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

Nie napisałeś accessorów, ale wziąłem pod uwagę enkapsulację, aby nie ujawniać ich wskaźników. Pod maską tej implementacji jest użycie klasy pośredniej , która nie znajduje się w łańcuchu dziedziczenia, ale łańcuch konwersji.

Activator jest to dobre, aby nie dodawać dodatkowych ograniczeń new(), ponieważ U są już ograniczone do Entity i mają parametryzowany konstruktor. To<U> chociaż jest odsłonięty, ale zapieczętowany bez ujawniania swojego konstruktora, może być utworzony tylko z operatora konwersji.

W kodzie testu, encja faktycznie przekształciła się w obiekt ogólny To<U>, a następnie typ docelowy, tak jest dodatkowa demonstracja z body do context. Ponieważ To<U> jest klasą zagnieżdżoną, może uzyskujemy dostęp do prywatnej Pointer klasy zawierającej, dzięki czemu możemy wykonywać rzeczy bez ujawniania wskaźnika.

To wszystko.
 0
Author: Ken Kin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-07 15:21:04

Możesz użyć 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>
    {
    }
 0
Author: Milad Sadeghi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-19 15:23:18