1

I don't know how I can make a generic settings class and hope that you can help me.
First of all I want a single settings file solution. For this I have created a Singleton like this:

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

But I think the better way is that the properties are in the classes and I can get the properties through reflection.
- Can you help me to implement something like this?
- Is it possible to give properties attributes like "encrypted = true"? - Whats the best way to save / load the settings in a xml file?

Updated
Here is a example how to use the settings actual:

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
  } 
subprime
  • 1,217
  • 8
  • 20
  • 34
  • You need store only string properties? – Толя Jan 16 '13 at 13:35
  • No, i store in general string, int, float, bool and generic stuff like List and Dictionary – subprime Jan 16 '13 at 13:43
  • A secondary question: What are these? Are these something like application settings, where you will be referencing them all at once, and using them to change the look/function of a general application? If so, there is some confusion with the definition of "Property", class property versus application property....you are pretty much completely implemented for that actually, what you have will work rather well. Could you add an example of how you want to be able to USE this functionality? – Nevyn Jan 16 '13 at 14:13
  • It might be easier to help you get what you want, how you want to use it, rather than giving you what you are asking for, which might not work the way you want it to... – Nevyn Jan 16 '13 at 14:18
  • Yes, i mean application properties. The standard way in visual studio for configurations is not pretty. – subprime Jan 16 '13 at 14:20

4 Answers4

2

This here is a rather relevant bit from my own code.

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

what it allows, is this:

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

Updated: More Explanation The way this works is to reflectively access properties, properties are different from fields in that they use getters and setters, as opposed to being directly declared and accessed. You can use this same method to get fields, or to also get fields, if you null check the return from GetProperty instead of simply assuming it works. Also, as was pointed out in another comment, this will break if you call it as is with a property that doesn't exist, because it lacks any form of error catching. I showed the code in its simplest possible form, not its most robust form.

As far as property attributes....that indexer needs to be created inside the class you want to use it with (or a parent class, I have it on my BaseObject), so internally you can implement attributes on given properties and then apply switches or checks against the properties when they are accessed. Maybe make all the properties some other custom class where you implement Object Value; Bool Encrypted; then work on it as needed from there, it really just depends on how fancy you want to get and how much code you want to write.

Nevyn
  • 2,623
  • 4
  • 18
  • 32
  • 3
    `GetValue(this, null)` is simpler than `.GetGetMethod().Invoke(this)` etc. Similarly, there's a `SetValue`. – Marc Gravell Jan 16 '13 at 13:31
  • In provided code used only string parameter. = 42 break compilation. – Толя Jan 16 '13 at 13:37
  • true. I can't remember now why I went with `GetGetMethod().Invoke`, I wrote this around 2 years ago and didn't comment on the function I used. It may have had something to do with mutli-threading and InvokeRequired...I simply cannot remember anymore. – Nevyn Jan 16 '13 at 13:37
  • Hi Nevyn, thanks for fast answer but i dont know the content. Can you explain and add more of your code? I searched a long time for a good Setting class but i dont find one :( – subprime Jan 16 '13 at 13:37
  • @subprime Edited and updated with a larger code sample and a longer explanation – Nevyn Jan 16 '13 at 13:42
  • @Nevyn Thanks, but the main problem is that i want collect all settings from the classes inthe application into one file. – subprime Jan 16 '13 at 13:57
  • well, I have to admit I slightly misunderstood what you were doing there. Sorry I wasn't that big a help, at least the accessor will come in handy :-) – Nevyn Jan 16 '13 at 14:10
  • Not going to delete this one, since its still decent code, but I did add another answer that better answers the question. – Nevyn Jan 16 '13 at 15:40
1

I not reccommend use Reflection in places where it possible do without it, as it very slow.

My example without reflection and Encryption prototype:

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);
        }
    }
}
Толя
  • 2,839
  • 15
  • 23
0

Use a dynamic class like this:https://gist.github.com/3914644 so you could access your properties as: yourObject.stringProperty or yourObject.intProperty

Diego Dias
  • 904
  • 3
  • 15
  • 23
0

One of the biggest issues is that there is no clean way to de-serialize an Object into an Object. if you dont know ahead of time what the Type of the object needs to be, its very hard to work with. So we have an alternate solution, store the type information.

Given that its not listed, I will provide what I consider an example XML, as well as a method of using it, and a method of accessing the properties themselves. The functions you are using for Get and Set properties are functional as is, and require no changes.

In the individual classes, you need to make sure the relevant properties in that class reference the Settings class in their own get/set methods

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

In your load and save functions, you will want to use Serialization, specifically, XmlSerializer. To do this, you need to declare your list of settings appropriately. For this I would actually use a custom class.

Updated to allow proper loading

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

Then, instead of just a dictionary, you would have something like:

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

your load function would be a simple de-serialize

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
}

the save function would essentially need to reverse it.

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
    }

and the xml would resemble something like this:

updated

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

I showed this example using the intermediate List<> because as it turns out you can't use anything that implements IDictionary with XmlSerializer. It will fail to initialize, it just doesn't work.

You can either create and maintain the list alongside the dictionary, or you can replace the dictionary with the List...make sure you have checks to verify that "Name" is unique, or you can simply ignore the list except during the Save and Load operations (which is how I wrote this example)

Update This really only works well with Primitive types (int, double, string, etc..), but because you directly store the type, you can use any custom type you want, because you know what it is and what to do with it, you just have to handle it in the set method of AttributeValue

Another Update: If you are only storing strings, instead of objects of all types...it gets ridiculously simpler. get rid of the XmlIgnore value AND the pType, then auto-implement AttributeValue. Boom, done. That will limit you to strings and other primitives though, make sure that the Get/Set for the values in other classes cast them appropriately...but it is a much simpler and easier implementation.

Nevyn
  • 2,623
  • 4
  • 18
  • 32