【问题标题】:Invoking a Cached Delegate when T is unknown当 T 未知时调用缓存委托
【发布时间】:2018-09-22 02:25:57
【问题描述】:

因此,我正在编写类似 ORM 的代码,该代码需要在不提前知道对象的情况下实例化和填充对象,使用反射 SetValue/GetValue 和属性。这行得通,但性能很糟糕,因为我需要创建新实例,然后为每个新“行”迭代它们的属性,并且对象可能也有需要创建/填充的嵌套对象。

所以我尝试使用和缓存 getter/setter 的委托,但我发现了一些问题。请仅查看 getter 的示例代码,我认为 setter 会更糟(对不起 VB 代码哈哈):

Public Class CommonDTO 
'actual objects
  Public Property UID As String
  Public Property CreateDate As DateTime
End Class

Public Class torm
  Private flags As BindingFlags = BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public
  Private common As Type = GetType(CommonDTO)
  'List for holding all the supported types, inside it contains many dictionaries holding the actual delegates
  Public listCacheGet As New ConditionalWeakTable(Of Type, Dictionary(Of String, [Delegate]))
  Private commonUID As PropertyInfo = Common.GetProperty("UID", flags)
  Private commonCreateDate As PropertyInfo = Common.GetProperty("CreateDate", flags)
  'Create the delegates for the get/set of each property. This is a royal PITA btw
  Public getcommonUID As Func(Of CommonDTO, String) = CType([Delegate].CreateDelegate(GetType(Func(Of CommonDTO, String)),
                                                                    commonUID.GetGetMethod(nonPublic:=True)), Func(Of CommonDTO, String))
  Public setcommonUID As Action(Of CommonDTO, String) = CType([Delegate].CreateDelegate(GetType(Action(Of CommonDTO, String)),
                                                                        commonUID.GetSetMethod()), Action(Of CommonDTO, String))
  Public getcommonCreateDate As Func(Of CommonDTO, DateTime) = CType([Delegate].CreateDelegate(GetType(Func(Of CommonDTO, DateTime)),
                                                           commonCreateDate.GetGetMethod(nonPublic:=True)), Func(Of CommonDTO, DateTime))
  Public setcommonCreateDate As Action(Of CommonDTO, DateTime) = CType([Delegate].CreateDelegate(GetType(Action(Of CommonDTO, DateTime)),
                                                                        commonCreateDate.GetSetMethod()), Action(Of CommonDTO, DateTime))

Public Sub New()
    Dim listCommonDTOGet As New Dictionary(Of String, [Delegate])
    'Fill the lists
    listCommonDTOGet.Add("UID", getcommonUID)
    listCommonDTOGet.Add("CreateDate", getcommonCreateDate)

    listCacheGet.Add(common, listCommonDTOGet)
End Sub
End Class

Public Class Test
Public Sub test(tdto as type)
    Dim orm As New torm
    Dim tempDTO = Activator.CreateInstance(tdto)
    Dim dictCache As IDictionary = Nothing

    'obtain the dictionary for the type that we are using
    orm.listCacheGet.TryGetValue(tdto, dictCache)

    '...but here is the problem. I don't know the exact type of the delegate 
    'contained within. I don't know how to instantiate it on its actual
    'Func/Action definition (because this procedure doesn't know it, 
    'and I think that T wouldn't work either, because the callers 
    'wouldn't know the actual Func/Action definition either, 
    'so I have only managed to create it as Object or [Delegate] 
    Dim getv as [Delegate] = dictCache.Item("UID")

    '...so getv ends up being an object that actually has the cached 
    'delegate and I have no idea how to invoke.
    'Well, I CAN do this, but it's even slower than using 
    'SetValue/GetValue!!
    Console.Writeline(getv.DynamicInvoke(tempDTO))

    'and of course, the direct calling is super fast
    Console.Writeline(orm.getcommonUID(tempDTO))
End Sub
End Class

我在这里缺少什么?我觉得这是我忽略的非常基本的东西,所以我会很感激任何指示。提前致谢。

【问题讨论】:

  • "对不起 VB 代码,哈哈"
  • @AndrewMortimer 很多人出于某种原因讨厌 VB。除此之外,我的代码很丑;)
  • 即使你不知道T,只要传入一个类型,你就可以在运行时在它上面实例化一个泛型类型。例如Dim myGeneric = GetType(GenericType(Of ))(在Of 后面故意留空)然后是Dim mySpecific = myGeneric.MakeGenericType(specificType)。我不确定这是否会帮助你。如果客户端代码知道类型,您还可以通过设置 getter/setter 委托来做一些其他很酷的事情。

标签: .net vb.net reflection delegates


【解决方案1】:

看起来太复杂了。如果您有明确知道对象类型的地方(类撕裂),则可以将它们直接投射到那里。并保留通用委托类型。

尝试使用这样的 setter/getter 来避免调用代码中的类型/依赖性

Public Delegate Function GetStringDelegate(Instance As Object) As String
Public Delegate Sub SetStringDelegate(Instance As Object, Value As String)

Public getCommonDTOUID As GetStringDelegate = Function(Instance) DirectCast(Instance, CommonDTO).UID
Public setCommonDTOUID As SetStringDelegate = Sub(Instance, Value) DirectCast(Instance, CommonDTO).UID = Value

我希望有一个 TypeName 列表 => 关于类型对象的 getters/setters 集合列表。

您也可以使用通用对象委托:

Public Delegate Function GetDelegate(Instance As Object) As Object
Public Delegate Sub SetDelegate(Instance As Object, Value As Object)

Public getCommonDTOUID As GetDelegate = Function(Instance) DirectCast(Instance, CommonDTO).UID
Public setCommonDTOUID As SetDelegate = Sub(Instance, Value) DirectCast(Instance, CommonDTO).UID = CStr(Value)

这取决于您的调用需求。字符串调用看起来像这样

Dim Value As String = DirectCast(dictCache.Item("UID"), GetStringDelegate)(tdto)

【讨论】:

  • 您的解决方案确实更易于实现......遗憾的是,即使使用委托,我的真实代码仍然非常慢,所以我猜这不是 get/set 用法。你的回答对我的问题是正确的。谢谢。
  • 这些代表应该尽可能快地达到(即使与动态生成的程序集相比)。尝试在部分代码周围使用一堆 StopWatch 来找出代码的慢部分。汇总所有迭代的时间。它可能会在其他地方放慢速度(listCacheGet.TryGetValue,dictCache.item)
  • 我正在这样做,因为我接受了您的回答。事实证明,使用委托确实提高了大约 10% 的一般调用,但问题在于 SSIS DataReader 需要花费大量时间来读取每个字段。我现在正在调查。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-21
  • 1970-01-01
  • 1970-01-01
  • 2010-09-17
  • 2016-04-06
  • 2014-03-18
相关资源
最近更新 更多