You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过TypeBuilder动态创建带Key属性的匿名类型以用于Linq GroupBy分组

如何为动态创建的匿名类型添加Key属性以支持Linq GroupBy?

你现在的代码已经能动态创建一个包含指定字段的类型,但要让它像VB中带Key修饰的匿名类型那样支持GroupBy,核心是要让这个动态类型正确实现相等性比较和哈希码计算——因为Linq的GroupBy依赖于这两个逻辑来判断两个实例是否属于同一分组。

VB里的Key关键字本质上是告诉编译器:基于这些属性生成Equals、GetHashCode方法,并实现IEquatable(Of T)接口。我们需要手动在动态类型中实现这些逻辑,下面是修改后的完整实现:

Imports System.Reflection
Imports System.Reflection.Emit
Imports System.Runtime.CompilerServices

Public Module DynamicTypeHelper
    ' 全局模块,用于动态类型生成(确保单例,避免重复创建)
    Private ReadOnly dynamicModule As ModuleBuilder = CreateDynamicModule()

    Private Function CreateDynamicModule() As ModuleBuilder
        Dim assemblyName As New AssemblyName("DynamicAnonymousTypes")
        Dim assemblyBuilder As AssemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run)
        Return assemblyBuilder.DefineDynamicModule("DynamicAnonymousTypesModule")
    End Function

    Public Shared Function CreateAnonymousType(fieldNames As List(Of String), fieldTypes As Type()) As Type
        ' 生成唯一的类型名称,避免冲突
        Dim anonymousTypeName As String = GetAnonymousTypeName(fieldTypes)
        Dim dynamicTypeBuilder As TypeBuilder = dynamicModule.DefineType(anonymousTypeName, TypeAttributes.Public Or TypeAttributes.Class Or TypeAttributes.Serializable)

        ' 1. 添加编译器生成特性,模拟匿名类型的标识
        dynamicTypeBuilder.SetCustomAttribute(New CustomAttributeBuilder(
            GetType(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes),
            New Object() {}))

        ' 2. 定义字段(如果需要属性的话,可以改成DefineProperty,这里先保持和你原代码一致用字段)
        Dim fields As New List(Of FieldInfo)()
        For i = 0 To fieldNames.Count - 1
            Dim fieldName = fieldNames(i)
            Dim fieldType = fieldTypes(i)
            Dim field = dynamicTypeBuilder.DefineField(fieldName, fieldType, FieldAttributes.Public)
            fields.Add(field)
        Next

        ' 3. 实现IEquatable(Of T)接口,T就是我们的动态类型
        Dim iEquatableType = GetType(IEquatable(Of )).MakeGenericType(dynamicTypeBuilder)
        dynamicTypeBuilder.AddInterfaceImplementation(iEquatableType)

        ' 4. 实现IEquatable(Of T).Equals(T)方法
        ImplementEqualsMethod(dynamicTypeBuilder, fields, iEquatableType)

        ' 5. 重写Object.Equals(Object)方法
        ImplementObjectEqualsOverride(dynamicTypeBuilder, fields)

        ' 6. 重写Object.GetHashCode()方法
        ImplementGetHashCodeOverride(dynamicTypeBuilder, fields)

        ' 创建并返回动态类型
        Return dynamicTypeBuilder.CreateType()
    End Function

    Private Sub ImplementEqualsMethod(typeBuilder As TypeBuilder, fields As List(Of FieldInfo), iEquatableType As Type)
        ' 定义方法签名:Public Function Equals(other As [动态类型]) As Boolean
        Dim equalsMethod = typeBuilder.DefineMethod("Equals", MethodAttributes.Public Or MethodAttributes.Virtual Or MethodAttributes.Final, GetType(Boolean), {typeBuilder})
        Dim il = equalsMethod.GetILGenerator()

        ' 如果other为Nothing,返回False
        il.Emit(OpCodes.Ldarg_1)
        il.Emit(OpCodes.Ldnull)
        il.Emit(OpCodes.Ceq)
        il.Emit(OpCodes.Ldc_I4_0)
        il.Emit(OpCodes.Ceq)
        Dim notNullLabel = il.DefineLabel()
        il.Emit(OpCodes.Brtrue_S, notNullLabel)
        il.Emit(OpCodes.Ldc_I4_0)
        il.Emit(OpCodes.Ret)

        il.MarkLabel(notNullLabel)

        ' 逐个比较所有Key字段(这里我们把所有字段当作Key)
        For Each field In fields
            ' 加载this.field
            il.Emit(OpCodes.Ldarg_0)
            il.Emit(OpCodes.Ldfld, field)
            ' 加载other.field
            il.Emit(OpCodes.Ldarg_1)
            il.Emit(OpCodes.Ldfld, field)

            ' 如果是值类型,直接用Ceq;如果是引用类型,调用Object.Equals
            If field.FieldType.IsValueType Then
                il.Emit(OpCodes.Ceq)
            Else
                il.Emit(OpCodes.Call, GetType(Object).GetMethod("Equals", {GetType(Object), GetType(Object)}))
            End If

            ' 如果比较结果为False,直接返回False
            il.Emit(OpCodes.Ldc_I4_0)
            il.Emit(OpCodes.Ceq)
            Dim fieldEqualLabel = il.DefineLabel()
            il.Emit(OpCodes.Brtrue_S, fieldEqualLabel)
            il.Emit(OpCodes.Ldc_I4_0)
            il.Emit(OpCodes.Ret)
            il.MarkLabel(fieldEqualLabel)
        Next

        ' 所有字段都相等,返回True
        il.Emit(OpCodes.Ldc_I4_1)
        il.Emit(OpCodes.Ret)

        ' 将方法映射到IEquatable接口的Equals方法
        typeBuilder.DefineMethodOverride(equalsMethod, iEquatableType.GetMethod("Equals"))
    End Sub

    Private Sub ImplementObjectEqualsOverride(typeBuilder As TypeBuilder, fields As List(Of FieldInfo))
        ' 重写Object.Equals(Object)
        Dim equalsMethod = typeBuilder.DefineMethod("Equals", MethodAttributes.Public Or MethodAttributes.Virtual Or MethodAttributes.Final, GetType(Boolean), {GetType(Object)})
        Dim il = equalsMethod.GetILGenerator()

        ' 检查是否是同一类型的实例
        il.Emit(OpCodes.Ldarg_1)
        il.Emit(OpCodes.Isinst, typeBuilder)
        Dim isSameTypeLabel = il.DefineLabel()
        il.Emit(OpCodes.Brfalse_S, isSameTypeLabel)
        ' 如果是同一类型,调用我们实现的IEquatable.Equals方法
        il.Emit(OpCodes.Ldarg_0)
        il.Emit(OpCodes.Ldarg_1)
        il.Emit(OpCodes.Castclass, typeBuilder)
        il.Emit(OpCodes.Callvirt, typeBuilder.GetMethod("Equals", {typeBuilder}))
        il.Emit(OpCodes.Ret)

        il.MarkLabel(isSameTypeLabel)
        ' 类型不同,返回False
        il.Emit(OpCodes.Ldc_I4_0)
        il.Emit(OpCodes.Ret)
    End Sub

    Private Sub ImplementGetHashCodeOverride(typeBuilder As TypeBuilder, fields As List(Of FieldInfo))
        ' 重写Object.GetHashCode()
        Dim hashCodeMethod = typeBuilder.DefineMethod("GetHashCode", MethodAttributes.Public Or MethodAttributes.Virtual Or MethodAttributes.Final, GetType(Integer), Type.EmptyTypes)
        Dim il = hashCodeMethod.GetILGenerator()

        ' 初始哈希码设为17
        il.Emit(OpCodes.Ldc_I4_S, 17)

        For Each field In fields
            ' 加载当前哈希码
            il.Emit(OpCodes.Ldc_I4_S, 23)
            il.Emit(OpCodes.Mul)
            ' 加载字段值
            il.Emit(OpCodes.Ldarg_0)
            il.Emit(OpCodes.Ldfld, field)

            ' 如果是值类型,直接调用GetHashCode;如果是引用类型,先检查是否为Nothing
            If field.FieldType.IsValueType Then
                il.Emit(OpCodes.Callvirt, field.FieldType.GetMethod("GetHashCode", Type.EmptyTypes))
            Else
                ' 如果字段为Nothing,用0作为哈希码;否则调用其GetHashCode
                Dim notNullLabel = il.DefineLabel()
                il.Emit(OpCodes.Dup)
                il.Emit(OpCodes.Ldnull)
                il.Emit(OpCodes.Ceq)
                il.Emit(OpCodes.Ldc_I4_0)
                il.Emit(OpCodes.Ceq)
                il.Emit(OpCodes.Brtrue_S, notNullLabel)
                il.Emit(OpCodes.Pop)
                il.Emit(OpCodes.Ldc_I4_0)
                il.Emit(OpCodes.Br_S, nextLabel)
                il.MarkLabel(notNullLabel)
                il.Emit(OpCodes.Callvirt, GetType(Object).GetMethod("GetHashCode", Type.EmptyTypes))
                nextLabel = il.DefineLabel()
                il.MarkLabel(nextLabel)
            End If

            ' 合并哈希码:current = current * 23 + field.GetHashCode()
            il.Emit(OpCodes.Add)
        Next

        ' 返回最终哈希码
        il.Emit(OpCodes.Ret)
    End Sub

    Private Function GetAnonymousTypeName(fieldTypes As Type()) As String
        ' 生成唯一名称,基于字段类型的全名
        Dim typeNames = fieldTypes.Select(Function(t) t.FullName.Replace(".", "_").Replace("`", "_")).ToArray()
        Return $"AnonymousType_{String.Join("_", typeNames)}_{Guid.NewGuid():N}"
    End Function
End Module

关键逻辑说明

  • 添加CompilerGeneratedAttribute:让动态类型和VB原生匿名类型保持一致的标识,虽非强制,但更贴近原生行为。
  • 实现IEquatable(Of T):提供强类型的相等比较,比Object.Equals性能更优,也是原生匿名类型的标准实现。
  • 重写Equals和GetHashCode:这是GroupBy正常工作的核心——GroupBy会通过这两个方法判断实例是否属于同一分组。示例中默认把所有字段当作Key属性处理,如果你需要区分Key/非Key字段,只需修改比较和哈希计算的逻辑,仅针对指定字段操作即可。
  • 字段 vs 属性:原代码使用DefineField,而VB原生匿名类型用的是属性。如果需要完全贴近原生,可将字段改为属性(使用DefineProperty),但示例中的字段实现也能正常工作。

使用示例

' 创建一个包含Name(String)和Age(Integer)的动态Key类型
Dim fieldNames = New List(Of String) From {"Name", "Age"}
Dim fieldTypes = {GetType(String), GetType(Integer)}
Dim keyType = CreateAnonymousType(fieldNames, fieldTypes)

' 在Linq GroupBy中使用
Dim data = New List(Of Object) From {
    New With {.Name = "Alice", .Age = 30, .Score = 90},
    New With {.Name = "Alice", .Age = 30, .Score = 85},
    New With {.Name = "Bob", .Age = 25, .Score = 95}
}

Dim grouped = data.GroupBy(Function(item)
                               ' 为每个数据项创建对应的动态Key实例
                               Return Activator.CreateInstance(keyType, item.Name, item.Age)
                           End Function)

这样GroupBy就能正确按照Name和Age分组,效果和使用VB原生的New With{Key .Name=..., Key .Age=...}完全一致。

内容的提问来源于stack exchange,提问作者Dionisis Hoholis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 11:22:44