Dynamically set property value in a class at runtime
Tags: csharp, dotnet
Published: 2023-02-02 20:29:33

## 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](https://social.technet.microsoft.com/wiki/contents/articles/54296.dynamically-set-property-value-in-a-class-c.aspx)) 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](https://github.com/karenpayneoregon/dynamic-property-value) which uses Microsoft Visual Studio 2022 17.4.x, .NET Core 7.

## Example

Using the following class and enum.

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

An application has defined an instance of Settings.

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

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.

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

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.

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

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.

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

### Note

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

```csharp
[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);
    
}
```

## Summary

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