다음을 통해 공유


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