Dynamically set property value in a class at runtime

Karen Payne - Feb 2 '23 - - Dev Community

About

Demonstrates setting a property value in an instance of a class at runtime.

Rarely needed as in C# object types and properties are known although there are rare occasions this may be needed when dealing with data received with a property name as type string and value of type object.

Note

When originally wrote this (for Microsoft TechNet) I thought it would be rarely needed but perhaps wrong as there are 14 forks on the repsoitory

Source code

Clone the following GitHub repository which uses Microsoft Visual Studio 2022 17.4.x, .NET Core 7.

Example

Using the following class and enum.

public class Settings
{
    public string UserName { get; set; }
    public int ContactIdentifier { get; set; }
    public MemberType MemberType { get; set; }
    public bool Active { get; set; }
    public DateTime Joined { get; set; }
}

public enum MemberType
{
    Gold,
    Silver,
    Bronze
}
Enter fullscreen mode Exit fullscreen mode

An application has defined an instance of Settings.

public Settings SingleSetting() =>
    new()
    {
        UserName = "Karen",
        MemberType = MemberType.Gold,
        ContactIdentifier = 1,
        Active = false,
        Joined = new DateTime(DateTime.Now.Year,DateTime.Now.Month,DateTime.Now.Day)
    };
Enter fullscreen mode Exit fullscreen mode

A request comes in to change UserName from Karen to Anne. This requires use of reflection where the majority of examples will have a method to perform assertions and setting of simple properties e.g. string, int, date. While this works up until a property type is enum for instance then assertion needs to be performed.

The following code has been written specifically for the class Settings where enum type is handled.

public static void SetValue(this Settings sender, string propertyName, object value)
{
    var propertyInfo = sender.GetType().GetProperty(propertyName);

    if (propertyInfo is null) return;

    var type = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;

    if (propertyInfo.PropertyType.IsEnum)
    {
        propertyInfo.SetValue(sender, Enum.Parse(propertyInfo.PropertyType, value.ToString()!));
    }
    else
    {
        var safeValue = (value == null) ? null : Convert.ChangeType(value, type);
        propertyInfo.SetValue(sender, safeValue, null);
    }
}
Enter fullscreen mode Exit fullscreen mode

Works although each time this needs to be done on other classes an extension must be written. Instead a generic extension method will handle different classes.

public static void SetValue<T>(this T sender, string propertyName, object value)
{
    var propertyInfo = sender.GetType().GetProperty(propertyName);

    if (propertyInfo is null) return;

    var type = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;

    if (propertyInfo.PropertyType.IsEnum)
    {
        propertyInfo.SetValue(sender, Enum.Parse(propertyInfo.PropertyType, value.ToString()!));
    }
    else
    {
        var safeValue = (value == null) ? null : Convert.ChangeType(value, type);
        propertyInfo.SetValue(sender, safeValue, null);
    }
}
Enter fullscreen mode Exit fullscreen mode

To change a property value by string name and object here done in test methods. Note SetValue extension method knows the type as if a known type was passed.

Using a unit test project.

namespace ShouldlyUnitTestProject
{
    [TestClass]
    public partial class MainTest : TestBase
    {
        [TestMethod]
        [TestTraits(Trait.SettingsClass)]
        public void Settings_UserNameChangeTest()
        {
            string expectedValue = "Anne";

            var setting = SingleSetting();
            setting.SetValue("UserName", "Anne");

            setting.UserName.ShouldBe(expectedValue);


        }
        [TestMethod]
        [TestTraits(Trait.SettingsClass)]
        public void Settings_ContactIdentifierChangeTest()
        {
            var expectedValue = 2;
            var setting = SingleSetting();
            setting.SetValue("ContactIdentifier", 2);
            setting.ContactIdentifier.ShouldBe(expectedValue);
        }

        [TestMethod]
        [TestTraits(Trait.SettingsClass)]
        public void Settings_MemberTypeChangeTest()
        {
            var expectedValue = MemberType.Bronze;
            var setting = SingleSetting();
            setting.SetValue("MemberType", MemberType.Bronze);
            setting.MemberType.ShouldBe(expectedValue);
        }

        [TestMethod]
        [TestTraits(Trait.SettingsClass)]
        public void Settings_ActiveChangeTest()
        {
            var setting = SingleSetting();
            setting.SetValue("Active", true);
            setting.Active.ShouldBe(true);
        }

        [TestMethod]
        public void Settings_JoinedChangeTest()
        {
            var expectedValue = new DateTime(Now.Year, Now.Month, Now.Day -1);

            var setting = SingleSetting();
            setting.Joined = new DateTime(Now.Year, Now.Month, Now.Day - 1);
            setting.SetValue("Joined", new DateTime(Now.Year, Now.Month, Now.Day - 1));
            setting.Joined.ShouldBe(expectedValue);

        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Note

In the example above I used strings for property names, feel free to use nameof also e.g.

[TestMethod]
[TestTraits(Trait.PersonClass)]
public void Person_FirstNameCheckIgnoreCase()
{
    var expectedValue = "tim";
    Person person = SinglePerson;

    person.SetValue(nameof(Person.FirstName), "Tim");
    person.FirstName.ShouldBe(expectedValue, 
        StringCompareShould.IgnoreCase);

}
Enter fullscreen mode Exit fullscreen mode

Summary

In this article an extension method has been presented to allow a property in a class to be changed.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .