errorcode007
errorcode007

Reputation: 331

Updating a referenced object in C#

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

Answers (4)

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

FMM
FMM

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

Ignacio Soler Garcia
Ignacio Soler Garcia

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

Andrew Bullock
Andrew Bullock

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

Related Questions