Эквивалент С++ 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;
}
но я не уверен, что это будет работать с объектами.