使用属性约束类型 - .NET

时间:2015-08-19 20:31:19

标签: c# .net f#

离开之前:虽然我的代码是在F#中,但这个问题适用于任何.NET语言。

所以这是我的情况 - 我在F#中有一个简单的ProgramOptions记录,用于保存命令行选项数据。每个字段代表一个不同的选项,它们可以具有默认值,这些默认值用自定义属性标记。

type ProgramOptionAttribute(defaultValue: obj) =
    inherit Attribute()

type ProgramOptions =
    { [<ProgramOption("render.pdf")>] output: string
      [<ProgramOption(true)>] printOutput: bool
      // several more
      }

在其他地方编写了一个不错的小函数,在给定原始命令行选项的情况下使用属性数据动态实例化此记录。这很好用。但是通过向属性提供一个与该字段不同的类型的对象来引入运行时类型不匹配非常容易(因为只有一个简单的转换来自obj - &gt;字段类型以后的行)。例如:

// Obviously wrong, but compiles without complaint, failing at runtime
[<ProgramOption(42)>] myField: bool

有没有办法让这种类型安全?某种通用的诡计?或者是我想要的不可能?

2 个答案:

答案 0 :(得分:4)

据我所知,这是不可能的,但即使是这样,它会带给你什么?您只能在属性中使用文字,因此您必须限制为stringintbool以及其他一些类型。

如果在某一天,您想要定义一个DateTimeOffset值的记录(不是一个不合理的情况)会怎样?

type myRecord = {
    [<ProgramOption(???)>]Date : DateTimeOffset
    [<ProgramOption("foo")>]Text : string }

您将ProgramOption放在Date元素上?

那就是说,你不一定要想出你自己的属性。 BCL已经定义了[<DefaultValue>] attribute,以及许多所谓的data annotations attributes。在我看来,它们都不适用于任何事物。例如,here's an in-depth explanation of why the Required attribute is redundant。那篇文章是关于面向对象的设计,但也适用于函数式编程。

在像F#这样的静态类型的函数式语言中,你应该make illegal states unrepresentable

最终,我认为更简单的方法将为每种类型定义默认值,如下所示:

type ProgramOptions = {
    Output: string
    PrintOutput: bool
    // several more
    }

let defaultProgramOptions = { Output = "render.pdf"; PrintOutput = true }

这可以让您根据默认值轻松创建值:

let myProgOps = { defaultProgramOptions with PrintOutput = false }

这是在编译时类型安全,并使用这些组成元素生成ProgramOptions值:

{ Output = "render.pdf"; PrintOutput = false; }

答案 1 :(得分:0)

我不确定这在编译时是否可行,但在运行时我会在类型之间转换数值,如果可能,或将所有值转换为字符串。来自更大程序的片段:

// set default values and parameters from attributes
    // this will override explicit calls to DefineParameters
    let props = 
      ty
          .GetProperties(BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Instance)
          .Where(fun p -> 
                  p.GetCustomAttributes<ParameterAttribute>(false) //.Count() > 0)
                    .SingleOrDefault(fun attr -> attr.UnitPeriod = OptionalValue.Missing || attr.UnitPeriod = OptionalValue(this.BarUnitPeriod)) <> Unchecked.defaultof<ParameterAttribute>)

    for p in props do
      let attr = p.GetCustomAttributes<ParameterAttribute>(false).SingleOrDefault(fun attr -> attr.UnitPeriod = OptionalValue.Missing || attr.UnitPeriod = OptionalValue(this.BarUnitPeriod)) //:?> ParameterAttribute , typeof<ParameterAttribute>
      if attr.IsString then
        this.DefineParameter(attr.Code, attr.DefaultValue :?> string, attr.Description)
      else
        this.DefineParameter(attr.Code, attr.DefaultValue :?> float, attr.Min, attr.Max, attr.Step, attr.Description)
        optPropInfos.Add(attr.Code, p)
      let value = this.GetParam(attr.Code) // this function gets a value from an attribute
      match p.PropertyType with
      | x when x = typeof<Int32> -> p.SetValue(this, Convert.ToInt32(value), null)
      | x when x = typeof<Int64> -> p.SetValue(this, Convert.ToInt64(value), null)
      | x when x = typeof<decimal> -> p.SetValue(this, Convert.ToDecimal(value), null)
      | x when x = typeof<float> -> p.SetValue(this, Convert.ToDouble(value), null)
      | x when x = typeof<string> -> p.SetValue(this, Convert.ToString(value), null)
      | _ -> failwith "ParameterAttribute could be applied to Int32/64, decimal, float or string types only"