Reputation: 331
I have a singleton object for settings in my project which, through references, I pass on. Now I use a MVVM approach in my application and only pass a copy of the object to the settings dialog. If the user clicks cancel nothing will ever get updated, but if the user presses OK I want to change the central settings object. If I do
(theLocalSettingsObject) = new Settings(... the values from the dialog);
the central object won't get updated.
I don't want to set every single property by hand, which however would work.
Upvotes: 1
Views: 185
Reputation: 796
You can use reflection. If you pack the code into a generic helper class, you can call like this
PropertyUpdater.Update(mainSettings).With(settingsFromDialog);
where the helper class looks like this
public class PropertyUpdater
{
public static PropertyUpdater<T> Update<T>(T objectToUpdate)
{
return PropertyUpdater<T>.Update(objectToUpdate);
}
}
public class PropertyUpdater<T>
{
private readonly T destination;
private PropertyUpdater(T destination)
{
this.destination = destination;
}
public static PropertyUpdater<T> Update(T objectToUpdate)
{
return new PropertyUpdater<T>(objectToUpdate);
}
public void With(T objectToCopyFrom)
{
PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo p in properties)
{
// Only copy properties with public get and set methods
if (!p.CanRead || !p.CanWrite || p.GetGetMethod(false) == null || p.GetSetMethod(false) == null) continue;
// Skip indexers
if (p.GetGetMethod(false).GetParameters().Length > 0) continue;
p.SetValue(this.destination, p.GetValue(objectToCopyFrom, null), null);
}
}
}
EDIT
So from ShellViewModel
that would be
private void SaveSettings(Settings settings)
{
PropertyUpdater.Update(this.Settings).With(settings);
}
Here's a passing unit test (MSTest).
[TestMethod]
public void TestMethod1()
{
var someClassInstance1 = new ClassA();
var someClassInstance2 = new ClassA();
var mainSettings = new Settings
{
SomeInt = 1,
SomeString = "2",
SomeObject = true,
SomeClass = someClassInstance1,
SomeArray = new[] { false }
};
var refToMainSettings = mainSettings;
var dialogSettings = new Settings
{
SomeInt = 2,
SomeString = "3",
SomeObject = 1.0,
SomeClass = someClassInstance2,
SomeArray = new[] { true, false }
};
// The magic method :-)
PropertyUpdater.Update(mainSettings).With(dialogSettings);
Assert.AreSame(refToMainSettings, mainSettings);
Assert.AreEqual(2, mainSettings.SomeInt);
Assert.AreEqual("3", mainSettings.SomeString);
Assert.IsInstanceOfType(mainSettings.SomeObject, typeof(double));
Assert.AreEqual(1.0, mainSettings.SomeObject);
Assert.AreSame(someClassInstance2, mainSettings.SomeClass);
Assert.AreEqual(2, mainSettings.SomeArray.Length);
Assert.AreEqual(true, mainSettings.SomeArray[0]);
Assert.AreEqual(false, mainSettings.SomeArray[1]);
}
public class Settings
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
public object SomeObject { get; set; }
public ClassA SomeClass { get; set; }
public bool[] SomeArray { get; set; }
public bool this[int i]
{
get { return SomeArray[i]; }
set { SomeArray[i] = value; }
}
}
public class ClassA {}
Upvotes: 0
Reputation: 4329
I agree with SoMoS in that singletons (in the way that I've often seen them) are indeed evil. The alternative I would recommend, however, is to harness dependency injection to ensure that everyone has a reference to the same object. Ninject, for instance, has a fluent binding for what it calls "singleton scope", where it ensures that the same object for a given type is bound every time an instance is requested. I have used this particular model extensively and it works great. You also have the added benefit that the consumers of your "singleton" don't need to know that it's a singleton, they are decoupled, and can be unit tested much easier.
Upvotes: 0
Reputation: 21855
I currently have the same scenario as you and I ended with creating a Copy method to copy all the values from the copy settings to the real settings.
I implemented the method calling the properties one by one thou, I don't see any problem with this.
realSettings.Copy(tempSettings);
Anyway I won't use singletons at all, they are evil
Upvotes: 0
Reputation: 37398
if youre going to use a singleton, then i suggest making it static
public static class Settings
{
public static string Propery { get; set; }
}
this way you dont need to pass references about,
you can just access Settings.Property
from anywhere.
Edit re your comments
if youre loading the settings from a database or something, make a static manager instead:
public static class SettingsManager
{
public static Settings Get()
{
// get settings from db, memory, wherever
}
}
Upvotes: 2