【问题标题】:Equivalent of C++'s reinterpret_cast in C#等效于 C# 中 C++ 的 reinterpret_cast
【发布时间】:2013-10-30 03:16:49
【问题描述】:

我想知道 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 会为空,因为它应该是。但如果是 C++,我本可以编写 Foo f = reinterpret_cast<Foo>(b); 并得到我想要的。在 C# 中我可以做些什么来实现同样的目标?

附言。我假设BaseFoo 在数据方面是一致的。

[更新]

这是一个简单的场景,reinterpret_cast 可能会有所帮助:

考虑编写一个 XXX-RPC 库,您无法控制传入的参数,也无法控制要调用的服务的签名。您的库应该使用给定的参数调用请求的服务。如果 C# 支持reinterpret_cast,我可以简单地将reinterpret_cast 给定的参数转换为预期的参数并调用服务。

【问题讨论】:

  • @JLott,没用。 C#C++ 更安全。
  • @JLott 我读过那篇文章,但它与我的要求完全不同。
  • @DmitryLedentsov AFAIK RPC 库通过序列化和反序列化实现这一点,使用字符串作为中间媒介。
  • 可以将字符串视为字节序列,因此通过自然的类比,您可以将字符串重新解释为对象。 c++ - 比如reinterpret_cast 本质上是不安全的,因为你很难说它是否工作正常。通过使用序列化(二进制,如果您需要一个紧凑的序列化),您可以获得对 filure 的细粒度控制,特别是对部分故障的控制。正如其他评论者所注意到的,.Net 类型系统应该是安全的 :)

标签: c# reinterpret-cast


【解决方案1】:

由于 b 只是 Base 的一个实例,因此您永远无法将其强制转换为 Foo 的非 null 实例。也许界面可能更适合您的需求?

【讨论】:

  • 感谢您的建议,但问题仍然存在。
【解决方案2】:

C# 在类型系统中没有允许您执行此操作的漏洞。它知道事物是什么类型,并且不允许您转换为不同的类型。原因很明显。向 Foo 添加字段时会发生什么?

如果你想要一个 Foo 类型,你需要创建一个 Foo 类型。更好的方法是创建一个以 Base 作为参数的 Foo 类型的构造函数。

【讨论】:

  • 如果发生数据不一致我会说它可能会抛出异常,它可能会返回 null 或...
  • 可以,但不行
  • 相反,当您转换为错误的类型时,它会这样做。在我看来更安全、更稳定。
【解决方案3】:

讨论

正如一些答案所指出的,.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 框架中使用的模式。如果您仍然坚持使用类似的东西,这里有一个使用紧凑序列化库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

如果您不想在合约中声明派生类型,请参阅this example...

如您所见,序列化非常紧凑。

如果你想使用更多的字段,你可以尝试隐式序列化字段:

[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]

一个通用的reinterpret_cast 绝对可以通过这个序列化解决方案或直接通过反射来实现,但我现在不会花时间。

【讨论】:

  • 如果你能在你的帖子中加入我们的一些对话,也许会很好。类似'因为它在 c# 中不受支持,所以这里是一个替代品......'。谢谢,太好了。
  • 最后一个想法,您可以从中创建一个通用方法,将两种类型从第一种转换为第二种。那么它会更接近C++的reinterpret_cast
  • 哦,忘了。还有AutoMapper,但据我所知,它只会映射属性,而不是字段,所有这些都是使用反射完成的。
【解决方案4】:

您也许可以在 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

未测试。

我猜,结构约束使你的问题无效,但它们是必要的,因为类是由 GC 管理的,所以你不能有指向它们的指针。

【讨论】:

    【解决方案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】:

    这行得通。是的,它与你想象的一样邪恶真棒。

    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,边界检查将阻止您访问超过T[] 原始长度的U 元素
    • 如果T 小于U,边界检查将允许您读取最后一个元素(实际上是缓冲区溢出漏洞)

    【讨论】:

    • 请注意,GetType 似乎仍然返回 TSource,所以还不够完整。
    • @NetMage 是的,运行时类型信息是一样的;这只是规避了编译时和 JIT 类型安全
    【解决方案7】:

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

    但我不确定这是否适用于对象。

    【讨论】:

      猜你喜欢
      • 2011-11-05
      • 2012-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-08
      • 1970-01-01
      相关资源
      最近更新 更多