Распаковка экземпляров разных типов с параметром универсального типа, который реализует интерфейс - PullRequest
0 голосов
/ 14 ноября 2018

Я работаю над абстракцией для варианта 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   

По сути, каждое значение может просматриваться несколькими ключами, а ключи для каждого значения генерируются с помощью IKeyGenerator с.Когда я вызываю Store на IKeyValueStore, я хочу найти все генераторы ключей для заданного значения, запустить каждый из них и сохранить каждый ключ для значения, чтобы затем его можно было извлечь любым из этих ключей.

Моя проблема в том, что, хотя я могу рефлексивно обнаружить все реализации IKeyGenerator, которые имеют параметр типа 'value, который соответствует типу 'value для этого IKeyValueStore, я не могу безопасно их распаковатьдля согласованного типа.Я попытался распаковать их все на IKeyGenerator<'value, IKey<'value>>, но это не работает, если конкретные реализации не реализуют интерфейс явно таким образом.Если они реализуют интерфейс, ссылаясь на конкретную реализацию IKey, распаковка завершается неудачно.

Затем я попытался ввести упрощенный интерфейс IKeyGenerator<'value>, который определял метод Generate как простой возврат IKey<'value> вместо 'key :> IKey<'value>, что решает проблему с распаковкой всех реализаций, но затем я сталкиваюсь спроблемы вниз по течению из-за незнания фактического типа ключа (например, для выполнения Find, когда есть несколько возможных ключей для этого значения).

Можно ли как-нибудь безопасно получить список IKeyGenerator экземпляров для различных реализаций IKey, при условии, что все они реализуют IKey<'value> для одного и того же типа значения?

1 Ответ

0 голосов
/ 14 ноября 2018

Я бы порекомендовал двухуровневую реализацию генераторов ключей: базовый класс, который реализует 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.

Таким образом, реализации могут иметь свои конкретные типы для работы, а потребитель будет иметь узкие типы, которые он ожидает.

Или, альтернативно (и я очень предпочитаю этот способ), есть функция для создания IKeyGenerator s:

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

P.S. Я знаю, что это не то, что вы просили, но я должен предостеречь от чрезмерного использования рефлексии и занятий. Это никогда не заканчивается хорошо. Рассмотрим более функциональный идиоматический подход.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...