​C# 反射结构体struct的一个坑​

本文探讨了在使用反射进行赋值时遇到的问题,尤其是在从struct到class对象的转换。作者揭示了struct和class的区别,以及装箱拆箱的概念,并提供了两种解决方案:一是先将struct转换为object,再进行赋值;二是利用FieldInfo.SetValueDirect方法进行直接操作。

今天代码用到了反射赋值,代码是这样写的:

 var objtype = obj.GetType();
 var Fieldinfo = objtype.GetField("I64");
 Fieldinfo.SetValue(obj, 100L);

当用户传进来的obj是class的时候无问题.但是传进来struct的时候,即不报错也不提示,但却什么值都没赋上!

经过多番查询.直到看到这个关于struct和class的区别:

http://www.cnblogs.com/gsk99/archive/2011/05/20/1904552.html

和这个装箱/拆箱的说明:

http://www.cnblogs.com/huashanlin/archive/2007/05/16/749359.html

其中有一段:

6:装箱/拆箱的内部操作。 
装箱: 
对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。 
第一步:新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。 
第二步:将值类型的实例字段拷贝到新分配的内存中。 
第三步:返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。 
有人这样理解:如果将Int32装箱,返回的地址,指向的就是一个Int32。我认为也不是不能这样理解,但这确实又有问题,一来它不全面,二来指向Int32并没说出它的实质(在托管堆中)。 
拆箱:
检查对象实例,确保它是给定值类型的一个装箱值。将该值从实例复制到值类型变量中。 
有书上讲,拆箱只是获取引用对象中指向值类型部分的指针,而内容拷贝则是赋值语句之触发。我觉得这并不要紧。最关键的是检查对象实例的本质,拆箱和装箱的类型必需匹配,这一点上,在IL层上,看不出原理何在,我的猜测,或许是调用了类似GetType之类的方法来取出类型进行匹配(因为需要严格匹配)。

我看了看我调用的:SetValue方法.第一个参数是个object,是引用类型,也就是说,当我调用的时候,其实net把我的struct复制了一份,然后在新的那份改了值,我这边这份当然是没有被动过的.

原因找到了,解决也就不难了,解决方案1:

在调用SetValue之前,就把我的struct转成object,然后调用完再转回来:

public struct MyStruct
    {
        public int TestInt;
    }

    class Program
    {
        static void Main(string[] args)
        {
            var Mystruct = new MyStruct();
            Type myType = typeof(MyStruct);
            FieldInfo myFieldInfo = myType.GetField("TestInt");
            Object someBoxedStruct = Mystruct;
            myFieldInfo.SetValue(someBoxedStruct, 1);
            MyStruct someUnBoxedStruct = (MyStruct)someBoxedStruct;
        }
   }

尝试了一下,是可以的.

还有一种方法,偶然搜索到的,把写入那部分改成这样:

FieldInfo fi = typeof(T).GetField(name, BindingFlags.Public | BindingFlags.Instance);
TypedReference reference = __makeref(obj);
fi.SetValueDirect(reference, x);

也是可以的.

<think>我们有一个需求:将C#结构体类型定义转换为表格形式展示。这通常用于生成文档或报告,以便更清晰地展示结构体的成员信息。思路:1.我们可以通过反射获取结构体的字段信息(名称、类型等),然后生成表格。2.也可以考虑使用自定义特性来为字段添加额外的描述(比如在表格中显示列标题、顺序等),但如果没有自定义特性,我们可以直接使用字段名和类型。步骤:假设我们有一个结构体,例如:```publicstructPoint{publicintX;publicintY;publicstringLabel;}```我们希望将其转换为如下表格形式(例如Markdown表格):|字段名|类型||--------|---------||X|int||Y|int||Label|string|或者更详细的表格,可能包括其他信息如访问修饰符等。我们可以编写一个辅助方法,通过反射获取结构体的所有公共字段(非静态),然后生成表格字符串。注意:结构体也可能包含属性,但问题中说的是结构体类型定义,通常包含字段。不过,根据需求,我们也可以考虑属性。这里我们以字段为例。代码实现:1.获取结构体的类型对象。2.使用`GetFields`方法获取所有公共实例字段(BindingFlags.Public|BindingFlags.Instance)。3.遍历这些字段,获取字段名和类型名。4.生成表格字符串(如Markdown表格或HTML表格)。示例代码:我们将编写一个方法,接受一个Type参数(结构体的类型),返回一个表示表格的字符串(Markdown格式)。注意:如果结构体是嵌套在其他类中,我们可能需要考虑访问权限,但这里假设我们只处理公共结构体。另外,我们也可以考虑处理属性(使用`GetProperties`)。根据需求,我们决定是显示字段还是属性,或者两者都显示。以下代码示例将生成Markdown表格:```csharpusingSystem;usingSystem.Reflection;usingSystem.Text;publicstaticclassStructToTableConverter{publicstaticstringConvertToMarkdownTable(TypestructType){//获取所有公共实例字段FieldInfo[]fields=structType.GetFields(BindingFlags.Public|BindingFlags.Instance);//如果没有字段,则返回空或提示if(fields.Length==0)return"该结构体没有公共字段。";StringBuildersb=newStringBuilder();//添加表头sb.AppendLine("|字段名|类型|");sb.AppendLine("|--------|------|");foreach(varfieldinfields){//对字段名和类型名进行转义,防止Markdown中的特殊字符stringfieldName=field.Name;stringtypeName=GetTypeName(field.FieldType);sb.AppendLine($"|{fieldName}|{typeName}|");}returnsb.ToString();}privatestaticstringGetTypeName(Typetype){//处理泛型类型,例如Nullable<int>,我们想显示为int?if(type.IsGenericType){//对于Nullable类型,特殊处理if(type.GetGenericTypeDefinition()==typeof(Nullable<>)){return$"{GetTypeName(type.GetGenericArguments()[0])}?";}//其他泛型类型,如List<string>,我们显示为List<string>stringgenericTypeName=type.Name.Split('`')[0];stringgenericArgs=string.Join(",",type.GetGenericArguments().Select(GetTypeName));return$"{genericTypeName}<{genericArgs}>";}//对于数组类型elseif(type.IsArray){intrank=type.GetArrayRank();stringarraySuffix=rank==1?"[]":newstring(',',rank-1).Insert(0,"[").Insert(1,"]");return$"{GetTypeName(type.GetElementType())}{arraySuffix}";}//简单类型else{//使用别名代替完整的命名空间switch(Type.GetTypeCode(type)){caseTypeCode.Boolean:return"bool";caseTypeCode.Byte:return"byte";caseTypeCode.Char:return"char";caseTypeCode.Decimal:return"decimal";caseTypeCode.Double:return"double";caseTypeCode.Int16:return"short";caseTypeCode.Int32:return"int";caseTypeCode.Int64:return"long";caseTypeCode.SByte:return"sbyte";caseTypeCode.Single:return"float";caseTypeCode.String:return"string";caseTypeCode.UInt16:return"ushort";caseTypeCode.UInt32:return"uint";caseTypeCode.UInt64:return"ulong";default:returntype.Name;//对于其他类型,返回类型名称(不包括命名空间)}}}}```使用方法:例如,对于上面定义的Point结构体,我们可以这样调用:```csharpstringtable=StructToTableConverter.ConvertToMarkdownTable(typeof(Point));Console.WriteLine(table);```输出:|字段名|类型||--------|--------||X|int||Y|int||Label|string|注意:上述方法中,我们处理了类型名称的显示,将一些内置类型用关键字表示,并且处理了可空类型、泛型和数组。如果需要更详细的信息(如字段的访问修饰符、是否只读等),可以扩展表格列。另外,如果结构体包含属性,我们可以修改代码,使用`GetProperties`方法,然后类似地处理。如果希望同时显示字段和属性,可以分别获取然后合并。但是,请注意结构体定义中通常使用字段(特别是为了性能),但属性也是常见的。所以我们可以根据需求调整。扩展:我们可以添加一个参数,让调用者选择是显示字段、属性还是两者都显示。例如:```csharppublicenumMemberType{Field,Property,All}publicstaticstringConvertToMarkdownTable(TypestructType,MemberTypememberType=MemberType.Field){//根据memberType参数决定获取字段还是属性}```这样更灵活。另外,我们也可以考虑使用第三方库来生成更复杂的表格(如Excel),但问题中要求的是表格形式展示,Markdown表格已经可以在很多地方渲染,也可以转换为其他格式。如果没有特殊要求,上述方法即可满足需求。注意:反射只能获取公共成员,非公共成员需要调整BindingFlags。但结构体通常使用公共字段,所以这里我们只获取公共实例成员。如果结构体有静态字段,我们排除了(BindingFlags.Instance)。总结:通过反射获取结构体的成员信息,然后格式化为表格字符串。另外,也可以使用T4模板或者SourceGenerators在编译时生成文档,但这超出了当前问题的范围。如果需要在运行时动态生成,反射是合适的。如果结构体定义在外部程序集,需要先加载。以上代码在.NETFramework和.NETCore中均可运行。注意:对于结构体,我们使用`Type.IsValueType`可以判断,但我们的方法也可以用于类(如果传入的是类类型)。如果只希望处理结构体,可以在方法开始检查:```csharpif(!structType.IsValueType){thrownewArgumentException("类型必须是结构体");}```但问题中要求的是结构体,所以我们可以加上这个检查。根据需求调整。</think>### 如何在 C# 中将结构体定义转换为表格形式展示 在 C# 中将结构体(`struct`)定义转换为表格形式,可通过以下两种核心方法实现: --- #### **方法 1:反射动态生成表格(运行时)** 通过反射获取结构体的字段/属性信息,动态生成表格数据: ```csharp using System; using System.Reflection; public static class StructTableConverter { public static void DisplayAsTable<T>() where T : struct { Type type = typeof(T); Console.WriteLine($"结构体名称: {type.Name}\n"); Console.WriteLine("| 成员名称 | 类型 | 是否只读 |"); Console.WriteLine("|----------|-----------|----------|"); // 遍历字段 foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { Console.WriteLine($"| {field.Name,-8} | {field.FieldType.Name,-9} | {field.IsInitOnly,-8} |"); } // 遍历属性 foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { Console.WriteLine($"| {prop.Name,-8} | {prop.PropertyType.Name,-9} | {prop.CanWrite ? "否" : "是",-8} |"); } } } // 示例结构体 public struct Point { public int X; public int Y; public readonly string Label; // 只读字段 public bool IsOrigin => X == 0 && Y == 0; // 只读属性 } // 调用方式 StructTableConverter.DisplayAsTable<Point>(); ``` **输出效果**: ``` 结构体名称: Point | 成员名称 | 类型 | 是否只读 | |----------|-----------|----------| | X | Int32 | False | | Y | Int32 | False | | Label | String | True | | IsOrigin | Boolean | 是 | ``` --- #### **方法 2:代码解析工具(设计时/编译时)** 使用 Roslyn 编译器 API 或第三方工具解析源代码: ```csharp using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; public static class CodeParser { public static void ParseStructToTable(string sourceCode) { SyntaxTree tree = CSharpSyntaxTree.ParseText(sourceCode); var root = tree.GetRoot(); var structs = root.DescendantNodes().OfType<StructDeclarationSyntax>(); foreach (var structDef in structs) { Console.WriteLine($"结构体名称: {structDef.Identifier.Text}\n"); Console.WriteLine("| 成员名称 | 类型 |"); Console.WriteLine("|----------|-----------|"); // 提取字段和属性 var members = structDef.Members.Where(m => m is FieldDeclarationSyntax or PropertyDeclarationSyntax); foreach (var member in members) { if (member is FieldDeclarationSyntax field) { foreach (var variable in field.Declaration.Variables) { Console.WriteLine($"| {variable.Identifier.Text,-8} | {field.Declaration.Type,-9} |"); } } else if (member is PropertyDeclarationSyntax prop) { Console.WriteLine($"| {prop.Identifier.Text,-8} | {prop.Type,-9} |"); } } } } } // 使用示例 string code = @" public struct Point { public int X; public int Y; public string Label { get; set; } }"; CodeParser.ParseStructToTable(code); ``` **输出效果**: ``` 结构体名称: Point | 成员名称 | 类型 | |----------|-----------| | X | int | | Y | int | | Label | string | ``` --- ### **应用场景与工具推荐** 1. **动态文档生成** 反射方案适合运行时生成文档(如 API 文档工具)。 2. **代码分析工具** Roslyn 方案适合: - IDE 插件开发(如 VS Extension) - 自定义代码生成器(Source Generators) 3. **第三方工具** - **Doxygen**:通过 XML 注释生成表格文档[^1] - **DocFX**:微软开源文档工具,支持表格渲染[^2] - **Markdown 生成器**:如 `StructToMarkdown` 库 --- ### **性能与注意事项** 1. **反射方案** - ✅ 灵活性强,支持运行时类型 - ❌ 性能开销较大(避免高频调用) 2. **Roslyn 方案** - ✅ 无运行时开销 - ❌ 需在编译时执行 3. **成员可见性** 调整 `BindingFlags` 控制可见性(如包含 `BindingFlags.NonPublic` 访问私有成员)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂奔De鸵鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值