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
}
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 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);
}
}
}
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);
}
Summary
In this article an extension method has been presented to allow a property in a class to be changed.