C#中类似于C++的reinterpret_cast的等效方法是什么?

13

我想知道在C#中与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为null,因为它应该是这样的。但如果是C ++,我可以编写Foo f = reinterpret_cast<Foo>(b);并得到我想要的结果。在C#中该怎么做才能实现相同的效果?

PS. 我假设BaseFoo 在数据方面是一致的。

[更新]

以下是一个简单的场景,在这种情况下,reinterpret_cast可能会有所帮助:

考虑编写一个XXX-RPC库,其中您无法控制传入参数或调用服务的签名。 库应该使用给定的参数调用要求的服务。 如果C#支持reinterpret_cast,我可以将给定的参数重新解释为期望的参数并调用服务。


这可能会有所帮助:https://dev59.com/W3RB5IYBdhLWcg3w4bEo - JLott
1
@JLott,这并不能帮助你。C#C++更加类型安全。 - Hamlet Hakobyan
@JLott,我已经阅读了那篇文章,但它与我的问题完全不同。 - Mehran
1
据我所知,RPC库通过序列化和反序列化实现这一点,使用字符串作为中间介质。 - Mehran
1
一个字符串可以被看作是一个字节序列,因此通过自然类比,你可以将字符串重新解释为一个对象。类似于 C++ 的 reinterpret_cast 是本质上不安全的,因为你几乎无法确定它是否正确工作。通过使用序列化(如果需要紧凑的序列化,则使用二进制),你可以获得对故障的细粒度控制,特别是部分故障。正如其他评论者所指出的那样,.Net 类型系统应该是安全的 :) - Dmitry Ledentsov
显示剩余4条评论
7个回答

12
这很有效。是的,它既邪恶又令人惊叹,就像你能想象到的那样。
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[]

  • 如果TU大,边界检查将防止您访问T[]原始长度之后的U元素。
  • 如果TU小,边界检查将允许您读取超出最后一个元素(实际上它是一种缓冲区溢出漏洞)。

请注意,GetType 仍然似乎返回 TSource,因此还不完整。 - NetMage
@NetMage 是的,运行时类型信息是相同的;这只是绕过了编译时和JIT类型安全性。 - Nick Strupat

8

讨论

正如一些答案所指出的那样,在问题的范围内,.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是可能的,但目前我不会花时间去投资。


1
也许在您的帖子中包含我们的一些对话内容会很不错,比如“由于在C#中不支持这个功能,所以可以使用替代方法...”。非常感谢,这非常好。 - Mehran
最后一点,您可以将其制作成通用方法,接受两种类型并将第一种类型转换为第二种类型。那么它会更接近于 C++ 的 reinterpret_cast - Mehran
哦,忘了提醒。还有AutoMapper,但它仅映射属性而不是字段,据我所知,它全都是使用反射完成的。 - Dmitry Ledentsov

3

您可以尝试在C#中使用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

未经测试。

我猜struct的限制使得你的问题没有意义,但是它们是必需的,因为类由GC管理,所以你不能有指向它们的指针。


2
如果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;            
    }

但我不确定这对对象是否有效。

1
这是我的“实现”。
[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));
    }

确保在调用时知道自己在做什么,特别是涉及到引用类型。


1
如果你看一眼我的答案,我正在做同样的事情,但不需要使用不安全的代码。 - mg30rg

1
C#没有类型系统中的漏洞,可以允许您这样做。它知道事物的类型,并且不会允许您将其转换为不同的类型。原因相当明显。如果向Foo添加字段会发生什么?
如果您想要Foo类型,则需要创建Foo类型。更好的方法是创建一个以Base作为参数的Foo类型的构造函数。

如果出现数据不一致的情况,我会建议抛出异常,返回 null 或者... - Mehran
它可能可以,但它并没有。 - Joel
1
相反,它会在你转换为错误类型时执行。在我看来,这样更安全、更稳定。 - Joel

0

由于 b 只是 Base 的一个实例,您永远无法将其转换为非空的 Foo 实例。也许接口更适合您的需求?


谢谢您的建议,但问题仍然存在。 - Mehran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接