Using Reflection to Set a Private Property in C#

本文介绍如何利用C#中的反射工具来测试类中的私有属性。通过具体案例展示,当需要修改不可直接访问的私有属性以进行单元测试时,可以采用反射的方法。这种方法虽然可能破坏封装性,但在某些特定场景下非常有用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

When testing a class, you often need to assign a value to a property - a property which you wouldn't otherwise want to allow to be set. In this scenario, reflection can be a valuable tool.

Reflection? What's that?

Reflection is a tool in C# that can be used to access the metadata of a class. It has a number of uses, but it should generally be used as a last resort, as there are some drawbacks. Using reflection can lead to some performance limitations. More importantly, reflection - as you'll see below - allows you to access properties marked as private, protected, internal, etc. Theoretically, those properties were marked as such for a reason, so it's not necessarily a great idea to go and mess with that encapsulation.

However, in this case, we have a good reason: testing. Test code can bend rules that production code cannot. Below is a good use-case scenario for reflection.

The Problem

Imagine a Member class in a gym's user management appliection. A Member probably has some basic properties like Name, Email, SubscriptionPlan, etc. Another important property might be DateJoined.

public class Member
{
  public string Name {get; set;}
  public string Email {get; set;}
  public SubscriptionPlan SubscriptionPlan {get; set;}
  public DateTime DateJoined {get; private set;}

  public Member(string name, string email, SubscriptionPlan subscriptionPlan)
  {
    Name = name;
    Email = email;
    SubscriptionPlan = subscriptionPlan;
    DateJoined = DateTime.Today;
  }

  // other code here
}

Above, DateJoined has a private setter and is set to DateTime.Today in the constructor. There is virtually no scenario in which you would want to change the value of DateJoined. Therefore, there is no reason to give this property a public setter and it is probably a bad idea to do so.

Alright, fair enough. But what if I want to test the CheckIfMemberNeedsAnniversaryGift method of AnniversaryGiftService? Now things get tricky.

public class AnniversaryGiftService
{
  public void SendGiftIfNeeded(Member member)
  {
    if (CheckIfMemberNeedsAnniversaryGift(member)) SendAnniversaryGift();
  }

  public bool CheckIfMemberNeedsAnniversaryGift(Member member)
  {
    return (member.DateJoined.Day == DateTime.Today.Day &&
      member.DateJoined.Month == DateTime.Today.Month);
  }

  public void SendAnniversaryGift()
  {
    // some code
  }
}

As you can see, the logic of CheckIfMemberNeedsAnniversaryGift is entirely dependent on member's DateJoined. But as discussed above, DateJoined has a private setter. So how can we test this method?

You guessed it - reflection!

The Solution

One implementation of reflection, which we will use here, involves using extension methods to set the private DateJoined property of Member:

public static class ReflectionHelperExtensionMethods
{
  // from https://stackoverflow.com/a/1565766/13680266
  public static void SetPrivateDateTimePropertyValue(this Member member, string propName, DateTime newValue)
  {
    PropertyInfo propertyInfo = typeof(Member).GetProperty(propName);
    if (propertyInfo == null) return;
    propertyInfo.SetValue(member, newValue);
  }
}

Essentially, the above code scans the metadata of the Member class to find a property of the name passed in. In this case, we would pass "DateJoined", and then the SetValue method above would set that property to newValue, without regard for that property being marked private.

If you're unfamiliar with extension methods, this article provides a nice overview of their use in tests.

Finally, here is the test we can write now that the reflection extension method is set up:

public static class AnniversaryGiftServiceCheckIfMemberNeedsAnniversaryGift
{
  // other code omitted

  [Test]
  public void ReturnsTrueGivenMemberWithDateJoined1YearAgo
  {
    var testMember = new Member("testName", "testEmail", "testSubscriptionPlan");

    // use reflection to set DateJoined property
    testMember.SetPrivateDateTimePropertyValue("DateJoined", DateTime.Today.AddYears(-1));

    var memberNeedsAnniversaryGift = _anniversaryGiftService.CheckIfMemberNeedsAnniversaryGift(testMember);

    Assert.True(memberNeedsAnniversaryGift);
  }
}

Conclusion

This was a pretty simple use case. In fact, we could have omitted the propName parameter of SetPrivateDateTimePropertyValue and renamed it to SetDateJoinedPropertyValue:

// from https://stackoverflow.com/a/1565766/13680266
public static void SetDateJoinedPropertyValue(this Member member, DateTime newValue)
{
  PropertyInfo propertyInfo = typeof(Member).GetProperty("DateJoined");
  if (propertyInfo == null) return;
  propertyInfo.SetValue(member, newValue);
}

However, this diminishes flexibility later on if Member gets more complex and has more DateTime properties with private setters, like a DateOfSubscriptionExpiration, for example. The propName parameter allows us to use the above method with more flexibility in a more complex and less contrived use case.

Resources

The StackOverflow thread that explains how to use reflection to set a private property, as we did above.

A C# Corner article explaining reflection and its uses in detail.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值