Dynamically set property value in a class (C#)
Introduction
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.
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
}
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)
};
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);
}
}
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);
}
}
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 System;
using BaseLibrary;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using ShouldlyUnitTestProject.Base;
using static System.DateTime;
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);
}
}
}
Caveat
There are Is properties to assist with determining a property type via PropertyInfo.
Summary
In this article an extension method has been presented to allow a property in a class to be changed.
External resource
Microsoft docs Reflection
Microsoft docs Accessing Attributes by Using Reflection
Microsoft docs PropertyInfo.SetValue Method
Source code
See the following GitHub repository