【问题标题】:C# Setting Properties using IndexC# 使用索引设置属性
【发布时间】:2010-04-04 14:14:44
【问题描述】:

我有一个业务类,其中包含用于各种证券交易所价格类型的许多属性。这是该类的一个示例:

public class Prices
{
    public decimal Today {get; set;}
    public decimal OneDay {get; set;}
    public decimal SixDay {get; set;}
    public decimal TenDay {get; set;}
    public decimal TwelveDay {get; set;}
    public decimal OneDayAdjusted {get; set;}
    public decimal SixDayAdjusted {get; set;}
    public decimal TenDayAdjusted {get; set;}
    public decimal OneHundredDayAdjusted {get; set;}
}

我有一个旧系统,它使用字符串 ID 来提供价格以识别价格类型。

例如

Today = "0D"  
OneDay = "1D"  
SixDay = "6D"  
//..., etc.   

首先,我将所有值加载到 IDictionary() 集合中,因此我们有:

[键] 值
[0D] => 1.23456
[1D] => 1.23456
[6D] => 1.23456
......等等。

其次,我使用将上述集合作为参数的方法设置价格类的属性,如下所示:

SetPricesValues(IDictionary<string, decimal> pricesDictionary)  
{  
    // TODAY'S PRICE  
    string TODAY = "D0";  
    if (true == pricesDictionary.ContainsKey(TODAY))  
    {  
        this.Today = pricesDictionary[TODAY];  
    }  
    // OneDay PRICE  
    string ONE_DAY = "D1";  
    if (true == pricesDictionary.ContainsKey(ONE_DAY))  
    {  
         this.OneDay = pricesDictionary[ONE_DAY];  
    }  
//..., ..., etc., for each other property   
}  

有没有更优雅的技术来设置大量属性? 谢谢, j

【问题讨论】:

  • 请不要写if (true == something_or_other)。 true == 完全是多余的,很伤人的眼睛。
  • 有些人可能认为它更清楚。虽然我同意你的观点,但它不会伤害我的眼睛(我见过更糟糕的代码,配得上“伤害”这个标题);-)
  • @Abel:按照你的逻辑,(true == (true == foo)) 不是更清楚吗?您也可以无限地继续它以获得无限清晰!
  • 哈哈,我自己并没有认为它更清楚,但有些人认为它更清楚(为什么不!false == ContainsKey())。同样的方式,他们可以使用== false 而不是! 或else。但是代码中有更明显的地方需要注意,这就是为什么 Guazz 提出了这个问题,我猜 :)

标签: c# coding-style properties


【解决方案1】:

不要使用字符串到十进制的映射并反复检查字典,而是使用委托映射/扩展方法:

public static class PriceConverter
{
    private static readonly Dictionary<string, Action<Prices, decimal>> setters =
        CreateSetterDictionary();

    public static void SetPrice(this Prices p, string id, decimal newPrice)
    {
        Action<Prices, decimal> setter;
        if (setters.TryGetValue(id, out setter))
            setter(p, newPrice);
    }

    private static Dictionary<string, Action<Prices, decimal>>
        CreateSetterDictionary()
    {
        var dic = new Dictionary<string, Action<Prices, decimal>>();
        dic.Add("0D", (p, d) => p.Today = d);
        dic.Add("1D", (p, d) => p.OneDay = d);
        // etc.
        return dic;
    }
}

那你可以写prices.SetPrice("0D", 1.23456)。

如果您愿意,可以在SetPrice 方法的末尾添加throw 语句来处理id 不匹配任何内容的情况。

【讨论】:

    【解决方案2】:

    我会将字符串变量放入常量中,而不是每次运行方法时都声明它们:

    private const string ONE_DAY = "D1";
    

    如果您希望集合参数包含所有或大部分可能的值,那么您的代码可能很酷。如果您希望字典将包含一小部分可能的值,那么使用 foreach 循环和 switch 语句来设置值可能更有效,而不是每次都查找每个可能的值。这仅取决于您需要处理多少值以及在每个方法调用中获得多少。

    【讨论】:

      【解决方案3】:

      在构造函数中定义属性字典,例如

      private Dictionary<int, PropertyInfo> propertyDictionary = new ...
      
      MyClass()
      {
          this.propertyDictionary.Add(0, this.GetType().GetProperty("FirstProperty");
          ...
      }
      

      然后使用索引属性访问

      decimal this[int index]
      {
          get
          {
              PropertyInfo property;
              if (this.propertyDictionary.TryGetValue(index, out property))
              {
                  // Not sure I remember the arguments right here:
                  property.SetValue(this, new object[] { value });
              }
          set
          {
              // Similar code
          }
      }
      

      您可以稍后通过使用反射自动解析构造函数中的属性来改进此代码, 添加具有告诉您 id 是什么的属性的所有属性。 (而不是在构造函数中手动添加它们)。

      【讨论】:

      • 索引可以是int/String/Whatever
      【解决方案4】:

      只是一个想法:

      interface IPrices_As_String{
       string OD { get; set; }
       // other properties here...
      }
      
      interface IPrices{
       decimal Today{get; set;}
      }
      
      class Prices : IPrices, IPrices_As_String{
       public decimal Today { get; set; }
       public string IPrices_As_String.OD {
        get { return this.Today.ToString(); }
        set { 
          if(!String.IsNullOrEmpty(value)){
             this.Today = decimal.Parse(value);
          }
        }
       }
      }
      

      然后,当我从遗留系统设置值时,我将使用接口上的价格类作为 IPrices_As_String,如:

      IPrices_As_String obj = new Prices();
      // set values from the legacy system
      
      IPrices obj2 = obj as IPrices; // will give me the correct object..
      

      .

      HTH。

      【讨论】:

      • 我认为这不会让事情变得更容易。您仍然需要至少与他当前的实现一样多的代码来设置值,除非您使用反射,在这种情况下,您可以只使用属性或映射数组在字符串键和字段之间进行映射。
      • @Matti - 也许我遗漏了一些东西,但我没有使用任何反射 - 我直接在集合中设置属性 &,将字符串转换为所需的相关类型......
      • string OD { get; set; } :你写的是O,而不是0,但是再看看原来的sn-p:这些前缀是数字。不幸的是,名字不能以数字开头。
      【解决方案5】:

      在我看来,你有几个选择,取决于你的技能,你被允许更改当前 POCO 或其他类的方式:

      • 如果您必须使用字典,请创建一个类似的字典,将“0D”等映射到 OneDay 名称。遍历字典并使用简单的反射进行分配。
      • 如果您可以更改读取数据的方式,请使用 OneDay 等读取字典,而不是仅适用于外部应用程序的“0D”。
      • 创建一个属性LegacyKeyAttribute,用这个属性扩充你的POCO gettors/settors。现在它变得微不足道:遍历 POCO 的属性,为您当前的旧密钥找到正确的属性。

      最后一个选项需要比许多普通程序员了解的更多的 C# 知识:编写和使用属性和反射。然而,最终它是最干净和最简单的解决方案(我会尝试举一个例子)。


      更新:这里有一个小例子。同时,已经发布了许多改进建议,但没有一个仍然使用属性,而您的情况似乎很理想。为什么?我相信它对现有代码的负担最小,而且它使阅读和理解您的代码更加容易。

      用法:

      // any price:
      Prices prices = new Prices();
      prices.SetPriceByLegacyName("0D", 1.2345M);
      
      // or, your loop becomes a bit easier:
      SetPricesValues(IDictionary<string, decimal> pricesDictionary)  
      {  
          foreach(string key in pricesDictionary.Keys)
          {
              // assuming "this" is of type Prices (you didn't specify)
              this.SetPriceByLegacyName(key, pricesDictionary[key]);
          }    
      }  
      

      实现:

      // the simplest attribute class is enough for you:
      [AttributeUsage(AttributeTargets.Property)]
      public class LegacyNameAttribute : Attribute
      {
          public string Name { get; set; }
          public LegacyNameAttribute(string name)
          {
              this.Name = name;
          }
      }
      
      // your Prices POCO class becomes easier to read
      public class Prices
      {
          [LegacyName("0D")]    public decimal Today { get; set; }
          [LegacyName("1D")]    public decimal OneDay { get; set; }
          [LegacyName("6D")]    public decimal SixDay { get; set; }
          [LegacyName("10D")]   public decimal TenDay { get; set; }
          [LegacyName("12D")]   public decimal TwelveDay { get; set; }
          [LegacyName("1DA")]   public decimal OneDayAdjusted { get; set; }
          [LegacyName("6DA")]   public decimal SixDayAdjusted { get; set; }
          [LegacyName("10DA")]  public decimal TenDayAdjusted { get; set; }
          [LegacyName("100DA")] public decimal OneHundredDayAdjusted { get; set; }
      }
      
      // an extension method to ease the implementation:
      public static class PricesExtensions
      {
          public static void SetPriceByLegacyName(this Prices price, string name, decimal value)
          {
              if (price == null)
                  throw new ArgumentException("Price cannot be null");
      
              foreach (PropertyInfo prop in price.GetType().GetProperties())
              {
                  LegacyNameAttribute legNameAttribute = (LegacyNameAttribute)
                      Attribute.GetCustomAttribute(prop, typeof(LegacyNameAttribute));
      
                  // set the property if the attribute matches
                  if (legNameAttribute != null && legNameAttribute.Name == name)
                  {
                      prop.SetValue(price, value, null);
                      break;   // nothing more to do
                  }
              }
          }
      }
      

      仅此而已。即使添加了所有行,您的总行数也可能会减少。但更重要的是,它变得更易于维护和使用。

      【讨论】:

      • 我担心这会因为所有的反射而变慢。有没有机会将此与 Aaronaught 的 lambdas 词典结合起来?
      • 当然可以优化,存储settor,缓存。但是,考虑到几乎每个数据到 POCO 库都使用反射,这是一个很好的起点。 “所有反射”是有限的:一次获取所有属性是唯一昂贵的调用,甚至不如反射中基于名称的查找那么昂贵。
      • 另外:速度优化只有在部分代码比其他代码慢时才有用。如果数据是通过 XML 检索然后映射,或者通过 ODBC 再映射,则上述方法的相对影响将仅为最低百分位数。
      • 是否可以创建一个代表的字典集合来实现这一点?例如。 Dictionary PropertySetter PropertySetter.Add("0D", new SetOneDay());
      • @Guazz:是的,这是可能的,但不是微不足道的,而且可能几乎没有必要。要获取设置器,请将“set_”添加到属性名称并改用GetMethod。在MethodInfo 上使用Invoke(PropertyInfo 是 MethodInfo)只比委托慢一点,并且更容易构建 MethodInfos 列表。但是在你做一些复杂的事情来实现一些简单的事情之前,问问自己这个问题是否值得。有什么收获?有必要吗?
      猜你喜欢
      • 2023-03-06
      • 2019-09-15
      • 2018-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多