【问题标题】:Best way to implement generic setting class - Get/Set Properties reflected?实现通用设置类的最佳方法 - 反映获取/设置属性?
【发布时间】:2012-12-30 20:06:00
【问题描述】:

我不知道如何制作通用设置类,希望您能帮助我。
首先,我想要一个单一的设置文件解决方案。为此,我创建了一个像这样的单例:

public sealed class Settings
{
  private static readonly Lazy<Settings> _instance = new Lazy<Settings>(() => new Settings());
  private Dictionary<string, object> m_lProperties = new Dictionary<string, object>();

  public void Load(string fileName)
  {
    throw new NotImplementedException();  
  }

  public void Save(string fileName)
  {
    throw new NotImplementedException();
  }

  public void Update()
  {
    throw new NotImplementedException();
  }

  /// <summary>
  /// Gets the propery.
  /// </summary>
  /// <param name="propertyName">Name of the property.</param>
  /// <returns></returns>
  public string GetPropery(string propertyName)
  {
    return m_lProperties[propertyName].ToString() ?? String.Empty;
  }

  /// <summary>
  /// Gets the propery.
  /// </summary>
  /// <param name="propertyName">Name of the property.</param>
  /// <param name="defaultValue">The default value.</param>
  /// <returns></returns>
  public string GetPropery(string propertyName, string defaultValue)
  {
    if (m_lProperties.ContainsKey(propertyName))
    {
      return m_lProperties[propertyName].ToString();
    }
    else
    {
      SetProperty(propertyName, defaultValue);
      return defaultValue;
    }
  }

  /// <summary>
  /// Sets the property.
  /// </summary>
  /// <param name="propertyName">Name of the property.</param>
  /// <param name="value">The value.</param>
  public void SetProperty(string propertyName, string value)
  {
    if (m_lProperties.ContainsKey(propertyName))
      m_lProperties[propertyName] = value;
    else
      m_lProperties.Add(propertyName, value);
  }
}

但我认为更好的方法是属性在类中,我可以通过反射获取属性。
- 你能帮我实现这样的事情吗?
- 是否可以提供诸如“encrypted = true”之类的属性属性? - 在 xml 文件中保存/加载设置的最佳方式是什么?

更新
这是一个如何使用实际设置的示例:

class Test()
{
  private string applicationPath;
  private string configurationPath;
  private string configurationFile;

  public Test()
  {
    applicationPath = Settings.Instance.GetPropery("ApplicationPath", AppDomain.CurrentDomain.BaseDirectory);
    configurationPath = Settings.Instance.GetPropery("ConfigurationPath", "configurations");  
    configurationFile = Settings.Instance.GetPropery("ConfigurationFile", "application.xml");  
    // ... Load file with all settings from all classes
  } 

【问题讨论】:

  • 您只需要存储字符串属性吗?
  • 不,我存储在一般字符串、int、float、bool 和通用的东西,如 List 和 Dictionary
  • 第二个问题:这些是什么?这些是否类似于应用程序设置,您将在其中一次引用它们,并使用它们来更改一般应用程序的外观/功能?如果是这样,那么“属性”的定义、类属性与应用程序属性之间存在一些混淆……实际上,您已经完全实现了这一点,您所拥有的将会很好地工作。您能否添加一个示例,说明您希望如何使用此功能?
  • 帮助你获得你想要的东西,你想如何使用它可能会更容易,而不是给你你想要的东西,这可能不会按照你想要的方式工作......
  • 是的,我的意思是应用程序属性。 Visual Studio 中用于配置的标准方式并不漂亮。

标签: c# xml reflection properties settings


【解决方案1】:

这是我自己的代码中相当相关的部分。

public class MyObject
{
    public string StringProperty {get; set;}

    public int IntProperty {get; set;}

    public object this[string PropertyName]
        {
            get
            {
                return GetType().GetProperty(PropertyName).GetGetMethod().Invoke(this, null);
            }
            set
            {
                GetType().GetProperty(PropertyName).GetSetMethod().Invoke(this, new object[] {value});
            }
        }
}

它允许的,是这样的:

MyObject X = new MyObject();
//Set
X["StringProperty"] = "The Answer Is: ";
X["IntProperty"] = 42;
//Get - Please note that object is the return type, so casting is required
int thingy1 = Convert.ToInt32(X["IntProperty"]);
string thingy2 = X["StringProperty"].ToString();

更新:更多解释 其工作方式是反射访问 properties,属性与字段的不同之处在于它们使用 getter 和 setter,而不是直接声明和访问。您可以使用相同的方法来获取字段,或者获取字段,如果您对 GetProperty 的返回值进行空检查,而不是简单地假设它有效。此外,正如另一条评论中所指出的,如果您使用不存在的属性原样调用它,这将中断,因为它缺少任何形式的错误捕获。我以最简单的形式展示了代码,而不是最健壮的形式。

至于属性属性....该索引器需要在您想要使用它的类中创建(或父类,我在我的BaseObject 上有它),因此您可以在内部实现属性给定属性,然后在访问属性时应用开关或检查属性。也许将所有属性设置为您实现Object Value; Bool Encrypted; 的其他自定义类,然后根据需要从那里处理它,这实际上仅取决于您想要获得多少花哨以及您想要编写多少代码。

【讨论】:

  • GetValue(this, null).GetGetMethod().Invoke(this) 等简单。同样,还有一个SetValue
  • 在提供的代码中仅使用字符串参数。 = 42 中断编译。
  • 真的。我现在不记得为什么选择GetGetMethod().Invoke,我大约在 2 年前写了这篇文章,并没有评论我使用的功能。它可能与多线程和 InvokeRequired 有关......我简直记不住了。
  • 嗨 Nevyn,感谢您的快速回答,但我不知道内容。您能解释并添加更多代码吗?我搜索了很长时间的一个好的设置类,但我没有找到一个:(
  • @subprime 编辑和更新了更大的代码示例和更长的解释
【解决方案2】:

我不建议在可能没有反射的地方使用反射,因为它非常慢。

我的例子没有反射和加密原型:

public sealed class Settings
{
    private static readonly HashSet<string> _propertiesForEncrypt = new HashSet<string>(new string[] { "StringProperty", "Password" });
    private static readonly Lazy<Settings> _instance = new Lazy<Settings>(() => new Settings());
    private Dictionary<string, object> m_lProperties = new Dictionary<string, object>();

    public void Load(string fileName)
    {
        // TODO: When you deserialize property which contains into "_propertiesForEncrypt" than Decrypt this property.
        throw new NotImplementedException();
    }

    public void Save(string fileName)
    {
        // TODO: When you serialize property which contains into "_propertiesForEncrypt" than Encrypt this property.
        throw new NotImplementedException();
    }

    public void Update()
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// Gets the propery.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    /// <returns></returns>
    public object GetPropery(string propertyName)
    {
        if (m_lProperties.ContainsKey(propertyName))
            return m_lProperties[propertyName];

        return null;
    }

    /// <summary>
    /// Gets the propery.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    /// <param name="defaultValue">The default value.</param>
    /// <returns></returns>
    public object GetPropery(string propertyName, object defaultValue)
    {
        if (m_lProperties.ContainsKey(propertyName))
        {
            return m_lProperties[propertyName].ToString();
        }
        else
        {
            SetProperty(propertyName, defaultValue);
            return defaultValue;
        }
    }

    /// <summary>
    /// Sets the property.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    /// <param name="value">The value.</param>
    public void SetProperty(string propertyName, object value)
    {
        if (m_lProperties.ContainsKey(propertyName))
            m_lProperties[propertyName] = value;
        else
            m_lProperties.Add(propertyName, value);
    }


    // Sample of string property
    public string StringProperty
    {
        get
        {
            return GetPropery("StringProperty") as string;
        }
        set
        {
            SetProperty("StringProperty", value);
        }
    }

    // Sample of int property
    public int IntProperty
    {
        get
        {
            object intValue = GetPropery("IntProperty");
            if (intValue == null)
                return 0; // Default value for this property.

            return (int)intValue;
        }
        set
        {
            SetProperty("IntProperty", value);
        }
    }
}

【讨论】:

    【解决方案3】:

    使用像这样的动态类:https://gist.github.com/3914644,这样您就可以访问您的属性:yourObject.stringProperty 或 yourObject.intProperty

    【讨论】:

    • 您能否提供一个代码示例来说明如何实际使用它。它看起来很有趣,但我不太明白如何初始化它以将属性放入类中......
    • 看这个:gist.github.com/4549270 要使用此类实现,您必须在 appSettings 标记内的 web.config 或 app.config 中包含所有设置
    • gist.github.com/4549270 未找到
    【解决方案4】:

    最大的问题之一是没有干净的方法将对象反序列化为对象。如果您不提前知道对象的类型需要是什么,那么它很难使用。所以我们有一个替代的解决方案,存储类型信息。

    鉴于它没有列出,我将提供我认为的示例 XML,以及使用它的方法,以及访问属性本身的方法。您用于 Get 和 Set 属性的函数按原样运行,无需更改。

    在各个类中,您需要确保该类中的相关属性在其自己的 get/set 方法中引用 Settings 类

    public int? MyClassProperty
    {
        get
        {
            return (int?)Settings.Instance.GetProperty("MyClassProperty");
        }
        set
        {
            Settings.Instance.SetProperty("MyClassProperty", value);
        }
    }
    

    在您的加载和保存功能中,您将需要使用序列化,特别是XmlSerializer。为此,您需要适当地声明设置列表。为此,我实际上会使用自定义类。

    已更新以允许正确加载

    public class AppSetting
    {
        [XmlAttribute("Name")]
        public string Name { get; set; }
        [XmlAttribute("pType")]
        public string pType{ get; set; }
        [XmlIgnore()]
        public object Value{ get; set; }
        [XmlText()]
        public string AttributeValue 
        {
            get { return Value.ToString(); }
            set {
            //this is where you have to have a MESSY type switch
            switch(pType) 
            { case "System.String": Value = value; break;
              //not showing the whole thing, you get the idea
            }
        }
    }
    

    然后,您将拥有类似以下的内容,而不仅仅是字典:

    public sealed class Settings
    {
      private static readonly Lazy<Settings> _instance = new Lazy<Settings>(() => new Settings());
      private Dictionary<string, object> m_lProperties = new Dictionary<string, object>();
      private List<AppSetting> mySettings = new List<AppSetting>();
    

    您的加载函数将是一个简单的反序列化

    public void Load(string fileName)
    {//Note: the assumption is that the app settings XML will be defined BEFORE this is called, and be under the same name every time.
        XmlSerializer ser = new XmlSerializer(typeof(List<AppSetting>));
        FileStream fs = File.Open(fileName);
        StreamReader sr = new StreamReader(fs);
        mySettings = (List<AppSetting>)ser.DeSerialize(sr);
        sr.Close();
        fs.Close();
    
        //skipping the foreach loop that will add all the properties to the dictionary
    }
    

    保存功能基本上需要反转它。

    public void Save(string fileName)
        {
            //skipping the foreach loop that re-builds the List from the Dictionary
            //Note: make sure when each AppSetting is created, you also set the pType field...use Value.GetType().ToString()
    
            XmlSerializer ser = new XmlSerializer(typeof(List<AppSetting>));
            FileStream fs = File.Open(fileName, FileMode.Create);
            StreamWriter sw = new StreamWriter(fs);
            //get rid of those pesky default namespaces
            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("", "");
            ser.Serialize(sw, mySettings, ns);
            sw.Flush();
            sw.Close();
            fs.Close();
            mySettings = null;//no need to keep it around
        }
    

    xml 会类似于这样:

    更新

    <ArrayOfAppSetting>
        <AppSetting Name="ApplicationPath" pType="System.String">C:\Users\ME\Documents\Visual Studio 2010\Projects\WindowsFormsApplication1\WindowsFormsApplication1\bin\Debug\</AppSetting> 
        <AppSetting Name="ConfigurationPath" pType="System.String">configurations</AppSetting> 
        <AppSetting Name="ConfigurationFile" pType="System.String">application.xml</AppSetting> 
        <AppSetting Name="prop" pType="System.Int32">1</AppSetting> 
    </ArrayOfAppSetting>
    

    我使用中间List&lt;&gt; 展示了这个示例,因为事实证明您不能使用任何通过XmlSerializer 实现IDictionary 的东西。它将无法初始化,它只是不起作用。

    您可以在字典旁边创建和维护列表,也可以将字典替换为列表...确保您已检查以确认“名称”是唯一的,或者您可以简单地忽略列表,除非在保存和加载操作(这就是我编写此示例的方式)

    更新 这确实只适用于原始类型(int、double、string 等)很好,但是因为您直接存储类型,所以您可以使用任何您想要的自定义类型,因为您知道它是什么以及怎么处理,只需要在AttributeValue的set方法中处理即可

    另一个更新:如果您只存储字符串,而不是所有类型的对象......它会变得非常简单。去掉XmlIgnore valuepType,然后自动实现AttributeValue。砰,完成。不过,这将限制您使用字符串和其他原语,请确保其他类中的值的 Get/Set 适当地转换它们......但它是一个更简单和更容易的实现。

    【讨论】:

      猜你喜欢
      • 2011-12-21
      • 2011-08-12
      • 1970-01-01
      • 2016-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-07
      相关资源
      最近更新 更多