实现泛型设置类的最佳方式-反映Get/Set属性

本文关键字:Get 反映 Set 属性 方式 设置 泛型 最佳 实现 | 更新日期: 2023-09-27 17:53:31

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

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);
  }
}

但我认为更好的方法是属性在类中,我可以通过反射获得属性。
-你能帮我实现这样的东西吗?
-是否有可能给属性属性像"加密=真"?在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
  } 

实现泛型设置类的最佳方式-反映Get/Set属性

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

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();

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

就属性属性而言....索引器需要在你想要使用它的类(或父类,我在我的BaseObject上有它)中创建,所以在内部你可以在给定的属性上实现属性,然后在访问属性时应用开关或检查。也许让所有的属性一些其他自定义类,你实现Object Value; Bool Encrypted;,然后在需要的时候从那里工作,它真的只是取决于你想要得到多少花式和你想写多少代码。

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

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

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);
        }
    }
}

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

最大的问题之一是没有干净的方法将Object反序列化为Object。如果你事先不知道对象的类型,那就很难处理了。所以我们有一个替代的解决方案,存储类型信息。

如果没有列出,我将提供我认为是一个示例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<>来展示这个示例,因为事实证明您不能使用任何与XmlSerializer一起实现IDictionary的东西。它将初始化失败,它只是不起作用。

你可以在字典旁边创建和维护列表,也可以用list代替字典…确保您有检查来验证"Name"是唯一的,或者您可以简单地忽略列表,除非在Save和Load操作期间(这就是我编写这个例子的方式)

这实际上只适用于基本类型(int, double, string等),但由于您直接存储了该类型,因此您可以使用任何您想要的自定义类型,因为您知道它是什么以及如何处理它,因此您只需在AttributeValue

的set方法中处理它

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