【问题标题】:Unboxing Instances of Different Types with a Generic Type Parameter that Implements an Interface使用实现接口的通用类型参数对不同类型的实例进行拆箱
【发布时间】:2018-11-14 15:33:47
【问题描述】:

我正在为键值存储样式 API 的变体进行抽象,其中我有以下接口(为清晰起见而简化)。

type IValue =
    abstract member Id: int64

type IKey<'value when 'value :> IValue> = interface end

type IKeyGenerator<'value, 'key when 'value :> IValue and 'key :> IKey<'value>> =
    abstract member Generate: 'value -> 'key

type IKeyValueStore<'value when 'value :> IValue> =
    abstract member Store: 'value -> unit
    abstract member Get: int64 -> 'value option
    abstract member Find<'key when 'key :> IKey<'value>> : 'key -> 'value option   

基本上每个值都可以通过多个key来查找,每个值的key都是由IKeyGenerators生成的。当我在IKeyValueStore 上调用Store 时,我想找到给定值的所有键生成器,运行它们中的每一个,并存储该值的每个键,以便随后可以通过这些键中的任何一个检索它。

我的问题是,虽然我可以反思地发现 IKeyGenerator 的所有实现,这些实现具有与此 IKeyValueStore'value 类型匹配的 'value 类型参数,但我无法安全地将它们拆箱到一致类型。我尝试将它们全部拆箱到IKeyGenerator&lt;'value, IKey&lt;'value&gt;&gt;,但如果具体实现没有以这种方式显式实现接口,这将不起作用。如果他们实现的接口引用了IKey 的特定实现,则拆箱失败。

然后我尝试引入一个简化的IKeyGenerator&lt;'value&gt; 接口,该接口将Generate 方法定义为简单地返回IKey&lt;'value&gt; 而不是'key :&gt; IKey&lt;'value&gt;,这解决了取消装箱所有实现的问题,但随后我在下游遇到了问题不知道键的实际类型(例如,当该值有多个可能的键时,为了执行Find)。

如果它们都为相同类型的值实现IKey&lt;'value&gt;,是否有某种方法可以安全地获取不同IKey 实现的IKeyGenerator 实例列表?

【问题讨论】:

    标签: .net generics f#


    【解决方案1】:

    我会推荐密钥生成器的两层实现:一个实现IKeyGenerator 的基类,但它本身具有更具体的类型参数,受制于那里的方式:

    type IKeyGenerator<'value when 'value :> IValue> =
        abstract member Generate: 'value -> IKey<'value>
    
    [<AbstractClass>]
    type KeyGeneratorBase<'value, 'key when 'value :> IValue and 'key :> IKey<'value>> =
        abstract member Generate: 'value -> 'key
    
        interface IKeyGenerator<'value> with
            override this.Generate v = this.Generate v :> _
    

    然后让具体的实现继承自KeyGeneratorBase

    这样,实现可以使用它们的具体类型,消费者将拥有它所期望的窄类型。

    或者,或者(我更喜欢这种方式),有一个函数来创建IKeyGenerators:

    let mkKeyGenerator<'value, 'key when 'value :> IValue and 'key :> IKey<'value>> (gen : 'value -> 'key) = 
        { new IKeyGenerator<_> with 
            member this.Generate v = gen v :> _
        }
    

    附:我知道这不是您所要求的,但我必须警告不要过度使用反射和类。这永远不会结束。考虑一种更实用、更惯用的方法。

    【讨论】:

    • 我在这里选择了第二种解决方案的变体,我只是用属性标记'value -&gt; 'key 的函数,然后在找到该属性时动态生成接口。我认为这将满足我的 OO 消费者,同时也使其成为 F# 用户的惯用语。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多