2015-08-19 52 views
4

在您離開之前:儘管我的代碼位於F#中,但此問題適用於任何.NET語言。使用屬性約束類型 - .NET

所以這裏是我的情況 - 我有一個簡單的ProgramOptions記錄在F#中用於存放命令行選項數據。每個字段代表一個不同的選項,並且它們可以具有默認值,這些默認值用自定義屬性標記。

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

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

我已經寫了一個漂亮的小功能,在其他地方動態實例使用的屬性數據這個記錄,因爲原始命令行選項。這很好。但是,通過向屬性提供一個對象來引入運行時類型不匹配是非常容易的,該屬性是與該字段不同的屬性(因爲obj - >字段類型只是簡單的強制轉換)。例如:

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

有沒有辦法讓這種類型安全?某種通用的欺騙?或者是我想要的不可能?

+1

我沒有時間寫一個正確的答案,但看看[在這個SO問題](http://stackoverflow.com/questions/294216/why-does-c -sharp-禁止,屬屬性類型)。當然,這是一個較老的問題,但它似乎並不表示您想要的是泛型。那裏的評論也表明,也許你可以用'typeof'等做一些事情。但即使如此,我也不相信你會得到你想要的編譯錯誤。 – Becuzz

回答

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" 
4

據我所知,這是不可能的,但即使它是,它會得到什麼?你只能在屬性中使用文字,所以你會受限於string,int,bool和其他一些類型。

如果在某一天你想要定義一個具有DateTimeOffset值的記錄(而不是不合理的情況)呢?

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

你會把哪個ProgramOptionDate元素?

這就是說,你不一定要拿出你自己的屬性。 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值與採取這些e組成元素:

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

這實際上是我的第一種方法 - 但是,我試圖使用屬性的主要原因是因爲它消除了重複。如果沒有動態創建的記錄,我必須多次指定每個程序選項的名稱,並且必須添加幾行以添加另一個。所以我想這歸結爲決定是否需要類型安全或更少的代碼重複... – Jwosty