Как создать Arbitrary для System.Type?

Я пытаюсь инициализировать объекты моей модели с помощью FsCheck. Модели живут на C# и обычно инициализируются через Entity Framework через их частные сеттеры. Например (придумано):

public class Model
{
    public string One { get; private set; } 
    public int Two { get; private set; } 
}

Я хотел бы создать генератор FsCheck, который автоматически использует зарегистрированный генератор для каждого свойства для создания модели. Что-то вроде этого:

let modelGenerator = 
    gen {
        let incident = new Model()
        typeof<Model>.GetProperties()
            |> Array.filter (fun p -> p.CanWrite)
            |> Array.iter (fun p -> 
                let! newVal = Arb.generateType p.PropertyType // I wish I could do this
                p.SetValue(incident, newVal))

        return incident
    }

В этом есть две ошибки:

  1. let! нельзя использовать вне вычислительного выражения gen.
  2. Arb.generateType не существует, и я не могу найти способ сделать его эквивалент

Можно ли создать генератор, который будет автоматически устанавливать приватные поля в моей модели?


person Daws    schedule 16.03.2015    source источник


Ответы (1)


С силой отражения все возможно (или бросает во время выполнения).

module Arb = 
    open System.Reflection 

    // this is just a helper type to do reflection on. 
    type internal GenerateInvoker = 
        static member Invoke<'typ> () = 
            Arb.generate<'typ>
            |> Gen.map box

    // Invokes a generic method using a runtime type as a generic argument.
    let generateType (typ: Type) =
        typeof<GenerateInvoker>
            .GetMethod("Invoke", BindingFlags.Static ||| BindingFlags.NonPublic)
            .MakeGenericMethod([|typ|])
            .Invoke(null, [||]) :?> Gen<obj>

let modelGenerator = 
    gen {
        let incident = new Model()
        let props =  
            typeof<Model>.GetProperties()
            |> Array.filter (fun p -> p.CanWrite)

        // gen builder implements For, so you can do something like this. 
        for prop in props do 
            let! newVal = Arb.generateType prop.PropertyType
            prop.SetValue(incident, newVal)

        return incident
    }

Gen.sample 1 3 modelGenerator
person scrwtp    schedule 17.03.2015
comment
Работал отлично, как только я добавил флаги привязки к вызову GetMethod: typeof<GenerateInvoker>.GetMethod("Invoke", BindingFlags.Static ||| BindingFlags.NonPublic). - person Daws; 17.03.2015
comment
@Daws: да, возможно, я слишком много возился с ответом после его тестирования. - person scrwtp; 17.03.2015