Эквивалент reinterpret_cast C++ в C#

Интересно, что эквивалентно reinterpret_cast C++ в C#!?

Вот мой образец:

class Base
{
    protected int counter = 0;
}

class Foo : Base
{
    public int Counter
    {
        get { return counter; }
    }
}

Base b = new Base();
Foo f = b as Foo; // f will be null

У меня нет возражений, почему f будет нулевым, поскольку так и должно быть. Но если бы это был C++, я мог бы написать Foo f = reinterpret_cast<Foo>(b); и получить то, что хотел. Что я могу сделать, чтобы добиться того же на С#?

ПС. Я предполагаю, что Base и Foo соответствуют данным.

[ОБНОВЛЕНИЕ]

Вот простой сценарий, в котором reinterpret_cast может быть полезен:

Рассмотрите возможность написания библиотеки XXX-RPC, в которой у вас нет контроля ни над входящими параметрами, ни над сигнатурой вызываемых служб. Ваша библиотека должна вызывать запрошенный сервис с заданными параметрами. Если бы С# поддерживал reinterpret_cast, я мог бы просто reinterpret_cast ввести заданные параметры в ожидаемые и вызвать службу.


person Mehran    schedule 21.10.2013    source источник
comment
Это может помочь: stackoverflow.com/questions/479705/reinterpret -приведение до-диез   -  person JLott    schedule 21.10.2013
comment
@JLott, это не может помочь. C# более типобезопасен, чем C++.   -  person Hamlet Hakobyan    schedule 21.10.2013
comment
@JLott Я прочитал этот пост, но он полностью отличается от того, о чем я спрашиваю.   -  person Mehran    schedule 21.10.2013
comment
Можете ли вы объяснить более подробно, чего вы хотите достичь таким образом?   -  person Hamlet Hakobyan    schedule 21.10.2013
comment
@Mehran Извините ... Я на самом деле не думаю, что это возможно :/   -  person JLott    schedule 21.10.2013
comment
большинство библиотек rpc можно использовать безопасным способом. Зачем сразу думать о reinterpret_cast, когда могут быть решения получше? См. protobuf, msgpack   -  person Dmitry Ledentsov    schedule 21.10.2013
comment
@DmitryLedentsov Библиотеки AFAIK RPC реализуют это посредством сериализации и десериализации, используя строку в качестве промежуточного носителя.   -  person Mehran    schedule 21.10.2013
comment
Строку можно рассматривать как последовательность байтов, поэтому по естественной аналогии вы переосмысливаете строку как объект. С++, как и reinterpret_cast, по своей природе небезопасен, так как вы вряд ли сможете сказать, правильно ли он работал. Используя сериализацию (бинарную, если вам нужна компактная), вы получаете более тонкий контроль над файлами, в частности, над частичным отказом. Как замечают другие комментаторы, система типов .Net должна быть безопасной :)   -  person Dmitry Ledentsov    schedule 22.10.2013
comment
@DmitryLedentsov Я полностью с вами согласен, в этом случае ответом на мой вопрос будет включение библиотеки, которая сериализует/десериализует (возможно, в/из двоичного, как вы указали), чтобы преобразовать один тип в другой. Если вы знаете о такой библиотеке, я буду более чем рад отметить ее как ответ. Спасибо.   -  person Mehran    schedule 22.10.2013


Ответы (7)


обсуждение

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

Как вы упомянули в обновлении, возможное использование может быть инфраструктурой RPC. Библиотеки RPC в любом случае обычно используют сериализацию/рефлексию, и есть несколько пригодных для использования:

так что, возможно, вы не захотите писать его сами.

Если ваш класс Base будет использовать общедоступные свойства, вы можете использовать AutoMapper:

class Base
{
    public int Counter { get; set; }
    // ...
}

...

AutoMapper.Mapper.CreateMap<Base, Foo>();
Foo foo = AutoMapper.Mapper.Map<Foo>(b);

Где Foo вообще не обязательно должно быть получено из Base. Он просто должен иметь свойство, которое вы хотите отобразить. Но опять же, вам может вообще не понадобиться два типа — решением может стать переосмысление архитектуры.

Как правило, нет необходимости использовать reinterpret_cast благодаря чистой архитектуре, которая хорошо вписывается в шаблоны, используемые в .Net Framework. Если вы все еще настаиваете на том, чтобы иметь что-то подобное, вот решение с использованием компактной библиотеки сериализации protobuf-net< /а>.

решение для сериализации

Ваши занятия:

using System;
using System.IO;
using ProtoBuf;
using ProtoBuf.Meta;

[ProtoContract]
[ProtoInclude(3, typeof(Foo))]
class Base
{
    [ProtoMember(1)]
    protected int counter = 0;

    public Base(int c) { counter = c; }
    public Base() { }
}

[ProtoContract]
class Foo : Base
{
    public int Counter { get { return counter; } }
}

и работающий пример сериализации-десериализации:

class Program
{
    static void Main(string[] args)
    {
        Base b = new Base(33);
        using (MemoryStream stream = new MemoryStream())
        {
            Serializer.Serialize<Base>(stream, b);
            Console.WriteLine("Length: {0}", stream.Length);
            stream.Seek(0, SeekOrigin.Begin);
            Foo f=new Foo();
            RuntimeTypeModel.Default.Deserialize(stream, f, typeof(Foo));
            Console.WriteLine("Foo: {0}", f.Counter);
        }
    }
}

вывод

Length: 2
Foo: 33

Если вы не хотите объявлять производные типы в своем контракте, см. этот пример...

Как видите, сериализация чрезвычайно компактна.

Если вы хотите использовать больше полей, вы можете попробовать неявную сериализацию полей:

[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]

Общий reinterpret_cast, безусловно, можно реализовать либо с помощью этого решения для сериализации, либо напрямую с помощью отражения, но в данный момент я бы не стал тратить время.

person Dmitry Ledentsov    schedule 22.10.2013
comment
Возможно, было бы неплохо, если бы вы включили часть нашего разговора в свой пост. Что-то вроде "поскольку это не поддерживается в С#, вот замена...". Спасибо, это здорово. - person Mehran; 22.10.2013
comment
И последнее: вы можете сделать из него общий метод, используя два типа, преобразующих первый во второй. Тогда он будет намного ближе к reinterpret_cast С++. - person Mehran; 22.10.2013
comment
о, забыл. Существует также AutoMapper, но он будет отображать только свойства, а не поля. , насколько я знаю, все делается с помощью рефлексии. - person Dmitry Ledentsov; 22.10.2013

Это работает. И да, это так зло и так прекрасно, как вы только можете себе представить.

static unsafe TDest ReinterpretCast<TSource, TDest>(TSource source)
{
    var sourceRef = __makeref(source);
    var dest = default(TDest);
    var destRef = __makeref(dest);
    *(IntPtr*)&destRef = *(IntPtr*)&sourceRef;
    return __refvalue(destRef, TDest);
}

Следует отметить, что если вы приводите T[] к и U[]:

  • Если T больше, чем U, проверка границ не позволит вам получить доступ к элементам U за пределами исходной длины T[].
  • Если T меньше, чем U, проверка границ позволит вам прочитать последний элемент (фактически это уязвимость переполнения буфера)
person Nick Strupat    schedule 06.02.2017
comment
Обратите внимание, что GetType по-прежнему возвращает TSource, так что это не совсем завершено. - person NetMage; 22.08.2019
comment
@NetMage да, информация о типе времени выполнения такая же; это просто обходит время компиляции и безопасность типа JIT - person Nick Strupat; 28.01.2020

Возможно, вы сможете добиться аналогичного поведения с блоками unsafe и void* в C#:

unsafe static TResult ReinterpretCast<TOriginal, TResult>(this TOriginal original)
    where TOriginal : struct
    where TResult : struct
{
    return *(TResult*)(void*)&original;
}

Применение:

Bar b = new Bar();
Foo f = b.ReinterpretCast<Foo>();
f = ReinterpretCast<Foo>(b); // this works as well

Не испытано.

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

person James Ko    schedule 07.08.2015

C# не имеет дыры в системе типов, которая позволила бы вам сделать это. Он знает, к какому типу относится объект, и не позволит вам привести его к другому типу. Причины этого достаточно очевидны. Что происходит, когда вы добавляете поле в Foo?

Если вам нужен тип Foo, вам нужно создать тип Foo. Что может быть лучшим маршрутом, так это создание конструктора типа Foo, который принимает Base в качестве параметра.

person Joel    schedule 21.10.2013
comment
В случае несоответствия данных я бы сказал, что он может вызвать исключение, он может вернуть ноль или... - person Mehran; 21.10.2013
comment
Могло бы, но не - person Joel; 21.10.2013
comment
Вместо этого он делает это, когда вы приводите к неправильному типу. На мой взгляд намного безопаснее и стабильнее. - person Joel; 22.10.2013

Это моя "реализация"

[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public unsafe static TResult ReinterpretCast<TOriginal, TResult>(/*this*/ TOriginal orig)
        //refember ReferenceTypes are references to the CLRHeader
        //where TOriginal : struct
        //where TResult : struct
    {
        return Read<TResult>(AddressOf(orig));
    }

Убедитесь, что вы знаете, что делаете, когда вызываете его, особенно со ссылочными типами.

person Jay    schedule 04.05.2016
comment
Если вы взглянете на мой ответ; Я делаю то же самое, но без требования небезопасного кода. - person mg30rg; 10.09.2019

Если бы Foo и Bar были структурами, вы могли бы сделать

    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
    public class MyFooBarHelper
    {
        [System.Runtime.InteropServices.FieldOffset(0)] public Foo theFoo;
        [System.Runtime.InteropServices.FieldOffset(0)] public Bar theBar;            
    }

но я не уверен, что это будет работать на объектах.

person mg30rg    schedule 09.11.2017

Поскольку b является только экземпляром Base, вы никогда не сможете привести его к ненулевому экземпляру Foo. Возможно, интерфейс может лучше соответствовать вашим потребностям?

person Bill Gregg    schedule 21.10.2013
comment
Спасибо за ваше предложение, но вопрос остается твердым. - person Mehran; 21.10.2013