C#:在运行时获取值类型变量的大小?

Posted

技术标签:

【中文标题】C#:在运行时获取值类型变量的大小?【英文标题】:C#: Getting size of a value-type variable at runtime? 【发布时间】:2011-12-31 16:12:07 【问题描述】:

我知道诸如 C 和 C++ 之类的语言允许在运行时使用 sizeof() 函数确定数据(结构、数组、变量...)的大小。我在 C# 中尝试过,显然它不允许将变量放入 sizeof() 函数中,但只能输入类型定义(float、byte、Int32、uint 等......),我应该怎么做?

实际上,我希望这种情况发生:

int x;
Console.WriteLine(sizeof(x));   // Output: 4

而不是:

Console.WriteLine(sizeof(int)); // Output: 4

我确信有一些正常的方法可以在 C# 中获取运行时数据的大小,但谷歌并没有提供太多帮助。这是我最后的希望

【问题讨论】:

你为什么需要那个? 您难道不知道吗,因为您要声明变量? @delnan:C 中的用例是,如果您将 x 的类型从 int 更改为 long long,则不必替换所有出现的 sizeof(int)使用sizeof(long long),您需要x 的大小。但是,我想不出很多需要 C# 中类型(或变量)大小的情况。 再加上var... 【参考方案1】:

要在运行时查找任意变量x 的大小,您可以使用Marshal.SizeOf:

System.Runtime.InteropServices.Marshal.SizeOf(x)

正如 dtb 所提到的,此函数在编组后返回变量的大小,但根据我的经验,这通常是您想要的大小,因为在纯托管环境中,变量的大小是兴趣不大。

【讨论】:

不是真的...Marshal.SizeOf 返回编组后的大小。例如,Marshal.SizeOf('x') 返回 1,而 sizeof(char) 返回 2。 - System.Runtime.InteropServices.Marshal.SizeOf(myObject) 'System.Runtime.InteropServices.Marshal.SizeOf(myObject)' 抛出类型为 'System.ArgumentException' int System.ArgumentException 不是一个好的答案;尝试在 bool 上执行此操作,看看会得到什么。【参考方案2】:

从Cory's answer 开始,如果性能很重要,并且您需要经常访问此代码,那么您可以缓存大小,以便每个类型只需要构建和执行一次动态方法:

int x = 42;
Console.WriteLine(Utils.SizeOf(x));    // Output: 4

// ...

public static class Utils

    public static int SizeOf<T>(T obj)
    
        return SizeOfCache<T>.SizeOf;
    

    private static class SizeOfCache<T>
    
        public static readonly int SizeOf;

        static SizeOfCache()
        
            var dm = new DynamicMethod("func", typeof(int),
                                       Type.EmptyTypes, typeof(Utils));

            ILGenerator il = dm.GetILGenerator();
            il.Emit(OpCodes.Sizeof, typeof(T));
            il.Emit(OpCodes.Ret);

            var func = (Func<int>)dm.CreateDelegate(typeof(Func<int>));
            SizeOf = func();
        
    

【讨论】:

我其实认为这是最好的答案;正如多人指出的那样,尺寸不会改变,而且 marshal 只是不正确。 IMO这是一个被低估的答案,所以我+1。 这是一个美女。非常感谢:) 奇怪的是,即使在编译器因为“托管”而拒绝发出 sizeof 的情况下,这段代码也确实给出了答案。我想知道这意味着什么。【参考方案3】:

int 的大小始终为 32 位。为什么需要在运行时获取大小?

话虽如此,您可以使用Marshal.SizeOf(),但这实际上仅适用于非托管代码。

我stumbled upon some code 显然会给你一个值类型的大小。它使用反射,与您想要使用的功能相比,这将是一个相当昂贵的方法调用 (sizeof()):

using System;
using System.Reflection;
using System.Reflection.Emit;

...

// GetManagedSize() returns the size of a structure whose type
// is 'type', as stored in managed memory. For any reference type
// this will simply return the size of a pointer (4 or 8).
public static int GetManagedSize(Type type)

    // all this just to invoke one opcode with no arguments!
    var method = new DynamicMethod("GetManagedSizeImpl", typeof(uint), new Type[0], typeof(TypeExtensions), false);

    ILGenerator gen = method.GetILGenerator();

    gen.Emit(OpCodes.Sizeof, type);
    gen.Emit(OpCodes.Ret);

    var func = (Func<uint>)method.CreateDelegate(typeof(Func<uint>));
    return checked((int)func());

【讨论】:

我不会说 Marshal.SizeOf 仅用于非托管代码。它是为 interop 设计的,因此是它的命名空间。有时互操作需要这样的东西。 请记住,marshal.sizeof 告诉您事物的大小在编组端而不是事物在托管端占用多少内存 .这两个值可能非常不同。 链接失效,typeof(TypeExtensions)编译失败=(【参考方案4】:

如果您正在执行诸如构建数据包以发送到设备之类的操作,请尝试以下操作:

byte[] dataBytes = BitConverter.GetBytes(x);
int dataLength = dataBytes.Length;

现在您可以,例如,将 dataBytes 数组复制到 dataPacket 数组的 Payload 部分,dataLength 会告诉您要复制多少字节,并让您验证或设置数据包中的 PayloadLength 值。

【讨论】:

【参考方案5】:
public static class TypeSize

    public static int GetSize<T>(this T value)
    
        if (typeof(T).IsArray)
        
            var elementSize = GetTypeSize(typeof(T).GetElementType());
            var length = (value as Array)?.GetLength(0);
            return length.GetValueOrDefault(0) * elementSize;
        
        return GetTypeSize(typeof(T));
    

    static ConcurrentDictionary<Type, int> _cache = new ConcurrentDictionary<Type, int>();

    static int GetTypeSize(Type type)
    
        return _cache.GetOrAdd(type, _ =>
        
            var dm = new DynamicMethod("SizeOfType", typeof(int), new Type[0]);
            ILGenerator il = dm.GetILGenerator();
            il.Emit(OpCodes.Sizeof, type);
            il.Emit(OpCodes.Ret);
            return (int)dm.Invoke(null, null);
        );
    

【讨论】:

【参考方案6】:

我要说的是使用类型推断来满足您的要求(“如果您将 x 的类型从 int 更改为 long long,您不必将每次出现的 sizeof(int) 替换为 sizeof(long long )"):

public unsafe void GetSizeOf<T>(T exemplar)
    where T : struct

    return sizeof(T);

但你不能这样做,因为 T 可能是一个“托管类型”——它可能是一个带有对象引用字段的结构。似乎没有办法将 T 限制为仅非托管类型。

您可以使用静态辅助类:

public static class Size

    public int Of(int x)
    
        return sizeof(int);
    

    public int Of(long x)
    
        return sizeof(long);
    

    public unsafe int Of(MyStruct x)
    
        //only works if MyStruct is unmanaged
        return sizeof(MyStruct);
    

public class Program

    public void Main()
    
        int x = 0;
        Console.WriteLine(Size.Of(x));
    
    public void OldMain()
    
        long x = 0;
        Console.WriteLine(Size.Of(x));
    

【讨论】:

不错。请注意,您的方法还需要为 static 才能以这种方式调用它们:static public int Of(int x)【参考方案7】:

继续并在 CORY 发布的代码中添加了一些安全/性能/便利功能,对于不那么偏执的 LukeH 的代码应该就足够了。

简而言之,这个类返回类型大小,确保尽可能使用缓存,同时包装来自外部类的异常。

您可能需要重写包罗万象的块以更好地适应您的项目。

/* A class for finding the sizes of types and variables */
public static class Sizes

    /* Retrieves the size of the generic type T
        Returns the size of 'T' on success, 0 otherwise */
    public static int SizeOf<T>()
    
        return FetchSizeOf(typeof(T));
    

    /* Retrieves the size of the type of obj
        Returns the size of 'obj' on success, 0 otherwise */
    public static int SizeOf<T>(T obj)
    
        return FetchSizeOf(typeof(T));
    

    /* Retrieves the size of 'type'
        Returns the size of 'type' on success, 0 otherwise */
    public static int SizeOf(this Type type)
    
        return FetchSizeOf(type);
    

    /* Gets the size of the specified type
        Returns the size of 'type' on success, 0 otherwise*/
    private static int FetchSizeOf(this Type type)
    
        if ( typeSizeCache == null )
            CreateCache();

        if ( typeSizeCache != null )
        
            int size = 0;
            if ( GetCachedSizeOf(type, out size) )
                return size;
            else
                return CalcAndCacheSizeOf(type);
        
        else
            return CalcSizeOf(type);
    

    /* Attempts to get the size of type from the cache
        Returns true and sets size on success, returns
        false and sets size to 0 otherwise. */
    private static bool GetCachedSizeOf(Type type, out int size)
    
        size = 0;
        try
        
            if ( type != null )
            
                if ( !typeSizeCache.TryGetValue(type, out size) )
                    size = 0;
            
        
        catch
        
            /*  - Documented: ArgumentNullException
                - No critical exceptions. */
            size = 0;
        
        return size > 0;
    

    /* Attempts to calculate the size of 'type', and caches
        the size if it is valid (size > 0)
        Returns the calclated size on success, 0 otherwise */
    private static int CalcAndCacheSizeOf(Type type)
    
        int typeSize = 0;
        try
        
            typeSize = CalcSizeOf(type);
            if ( typeSize > 0 )
                typeSizeCache.Add(type, typeSize);
        
        catch
        
            /*  - Documented: ArgumentException, ArgumentNullException,
                - Additionally Expected: OutOfMemoryException
                - No critical exceptions documented. */
        
        return typeSize;
    

    /* Calculates the size of a type using dynamic methods
        Return the type's size on success, 0 otherwise */
    private static int CalcSizeOf(this Type type)
    
        try
        
            var sizeOfMethod = new DynamicMethod("SizeOf", typeof(int), Type.EmptyTypes);
            var generator = sizeOfMethod.GetILGenerator();
            generator.Emit(OpCodes.Sizeof, type);
            generator.Emit(OpCodes.Ret);

            var sizeFunction = (Func<int>)sizeOfMethod.CreateDelegate(typeof(Func<int>));
            return sizeFunction();
        
        catch
        
            /*  - Documented: OutOfMemoryException, ArgumentNullException,
                              ArgumentException, MissingMethodException,
                              MethodAccessException
                - No critical exceptions documented. */
        
        return 0;
    

    /* Attempts to allocate the typeSizesCache
        returns whether the cache is allocated*/
    private static bool CreateCache()
    
        if ( typeSizeCache == null )
        
            try
            
                typeSizeCache = new Dictionary<Type, int>();
            
            catch
            
                /*  - Documented: OutOfMemoryException
                    - No critical exceptions documented. */
                typeSizeCache = null;
            
        
        return typeSizeCache != null;
    

    /* Static constructor for Sizes, sets typeSizeCache to null */
    static Sizes()
    
        CreateCache();
    

    /* Caches the calculated size of various types */
    private static Dictionary<Type, int> typeSizeCache;

【讨论】:

【参考方案8】:

netcore1.0 开始,System.Runtime.CompilerServices.Unsafe.SizeOf 方法可以为您提供任何对象的大小。方法是在运行时实现的,所以应该很快。

注意:该方法似乎返回引用类型的指针大小(sizeof(IntPtr))而不是实际大小

用法:

Console.WriteLine(Unsafe.SizeOf<System.Guid>()); // 16

【讨论】:

我不喜欢“不安全”这个名字,你能解释一下使用这种方法是否有任何可能的危险吗?文档没有提到任何东西。 @Eboubaker,这种特殊方法是安全的,不会造成任何不良后果。在内部它只是sizeof type; ret,请参阅source。此方法位于 Unsafe 类中,因为 C# 不允许在不安全上下文之外的任意类型上使用 sizeof。可能是因为手写序列化,开发者可能不小心做错了

以上是关于C#:在运行时获取值类型变量的大小?的主要内容,如果未能解决你的问题,请参考以下文章

C#编译和运行原理

C#变量常量数据类型数据转换

C#:在运行时获取类型参数以传递给通用方法[重复]

C# “值类型“和“引用类型“在内存的分配

C# “值类型“和“引用类型“在内存的分配

《精通C#》第十六章-动态类型和动态语言运行时-第一节至第四节