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

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

Здесь мой пример:

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); и получить то, что я хотел. Что я могу сделать, чтобы добиться того же результата в С#?

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

[ОБНОВИТЬ]

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

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

Ответ 1

обсуждение

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

Ответ 2

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

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[] to и U[]:

  • Если T больше, чем U, проверка границ не позволит вам получить доступ к элементам U, предшествующим исходной длине T[]
  • Если T меньше, чем U, проверка границ позволит вам прочитать прошлый элемент (фактически его уязвимость переполнения буфера).

Ответ 3

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

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, поэтому вам не разрешено иметь указатели на них.

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

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