Improved Private Accessor for Static Classes

Back in 2013, I presented a way to create private accessors in case using publicize.exe is not an option. If you have not read that post yet, please do so to get the context of this post.

I will not repeat here why I think private accessors are essential for high code coverage when doing white box testing. My mind hasn’t changed on that.

The Pain Point

Using my initial version of PrivateAccessorBase for static classes, Visual Studio always throws the error CS0718 ‘type’: static types cannot be used as type arguments.

To solve this, I used to put a preprocessor directive around the class declaration so that in Debug build the class was not declared as static.

// Preprocessor directive to have non-static declaration in debug build

#if DEBUG
  public class StaticClassWithNonPublicMembers
#else
  public static class StaticClassWithNonPublicMembers
#endif // DEBUG
{
…
}

It was also required to create an internal constructor for the accessor class, which looked like this

#region Internal Constructors

/// <summary>
/// Initializes a new instance of <see cref="StaticClassWithNonPublicMembersAccessor"/> with an instance of the class to be tested.
/// </summary>
internal StaticClassWithNonPublicMembersAccessor()
  : base(new StaticClassWithNonPublicMembers())
{
// intentionally left blank
}

#endregion Internal Constructors

When looking into the implementation of PrivateAccessorBase<T>, we can see that there is no need to have an instance of the static class to be tested. In its static constructor, PrivateAccessorBase<T> uses the type of the passed type argument to retrieve the TypeInfo, which is required to get the list of the declared properties and methods of the class to be tested. But it does not need an instance of the class, as long as it is static.

// Not so elegant version of PrivateAccessorBase<T>'s static constructor

/// <summary>
/// Static initialization.
/// </summary>
static PrivateAccessorBase()
{
  // Get the type info.
  TypeInfo typeInfo = typeof(T).GetTypeInfo();

  // Get the declared properties for later use, in case static properties should be accessed.
  PrivateAccessorBase<T>.DeclaredProperties = typeInfo.DeclaredProperties;

  // Get the declared methods for later use, in case static methods should be accessed.
  PrivateAccessorBase<T>.DeclaredMethods = typeInfo.DeclaredMethods;
}

Having a preprocessor directive to solve this issue is far away from my understanding of elegance, but for whatever reason I accepted this for a (too) long time.

The Improvement

Reading Jon Skeet’s suggestion to use a Type parameter instead of a type argument in Stack Overflow: C# – static types cannot be used as type arguments, it was clear to me what needs to be changed to keep static classes static and get rid of the preprocessor directive.

I created a new private accessor base class which can be used for static classes only (well, it can be used for non-static classes too, but one will not be able to access non-static members). Instead of having a type argument, a Type parameter is passed to its non-static constructor (has to be non-static, there is no way to pass parameters to a static constructor, as it is called automatically by the CLR, see Static Constructors (C# Programming Guide)).

Here comes the new base class with methods to get and set static non-public properties and one method to invoke non-public static void methods with parameters. Like the code of the original post, this sample is not complete. E.g., a method to invoke methods having a return value is missing. I leave it up to you to add whatever you need 😉

/// <summary>
/// Base class for private accessors that provides access to non-public members of static classes.
/// </summary>
/// <remarks>
/// written 2023 by Instance Factory, a project of the proccelerate GmbH.
/// </remarks>
internal abstract class PrivateStaticAccessorBase
{
  /// <summary>
  /// Gets / sets the declared proporties for later use, in case static properties should be accessed.
  /// </summary>
  private IEnumerable<PropertyInfo> DeclaredProperties { get; }

  /// <summary>
  /// Gets / sets the declared methods for later use, in case static methods should be accessed.
  /// </summary>
  private IEnumerable<MethodInfo> DeclaredMethods { get; }

  /// <summary>
  /// Initializes a new instance of <see cref="PrivateStaticAccessorBase"/> with the type of the (static) class to be tested.
  /// </summary>
  /// <param name="typeToBeAccessed">Type of the (static) class to be tested</param>
  internal PrivateStaticAccessorBase
    (
    Type typeToBeAccessed
    )
  {
    // Get the type info.
    TypeInfo typeInfo = typeToBeAccessed.GetTypeInfo();

    // Get the declared properties for later use, in case static properties should be accessed.
    DeclaredProperties = typeInfo.DeclaredProperties;

    // Get the declared methods for later use, in case static methods should be accessed.
    DeclaredMethods = typeInfo.DeclaredMethods;
  }

  /// <summary>
  /// Returns the value of a static property. The name of the property is passed by using <see cref="CallerMemberName"/> (for .NET version >= 4.5).
  /// </summary>
  /// <typeparam name="TReturnValue">Type of the return value.</typeparam>
  /// <param name="callerName">Name of the calling method.</param>
  /// <returns>The property value.</returns>
  protected TReturnValue GetStaticPropertyValue<TReturnValue>
      (
      [CallerMemberName] string callerName = null
      )
  {
      // Find the property having the matching name and get the value
      return ((TReturnValue) DeclaredProperties.Single(info => info.Name.Equals(callerName)).GetValue(null));
  }

  /// <summary>
  /// Sets the value of a static property. The name of the property is passed by using <see cref="CallerMemberName"/> (for .NET version >= 4.5).
  /// </summary>
  /// <param name="value">The value to be set.</param>
  /// <param name="callerName">Name of the calling method.</param>
  protected void SetStaticPropertyValue
    (
    object value,
    [CallerMemberName] string callerName = null
    )
  {
      // Find the property having the matching name and set the value
      DeclaredProperties.Single(info => info.Name.Equals(callerName)).SetValue(null, value);
  }

  /// <summary>
  /// Invokes a static method with parameters and no return value.
  /// </summary>
  /// <param name="callerName">Name of the calling method.</param>
  /// <param name="parameter">The parameter to be passed.</param>
  protected void InvokeStatic
      (
      [CallerMemberName] string callerName = null,
      params object[] parameter
      )
  {
      DeclaredMethods.Single(info => info.Name.Equals(callerName)).Invoke(null, parameter);
  }
}

Please note that using the PrivateAccessorBase<T> to access static members of non-static classes of course still works and should be used. PrivateStaticAccessorBase is intended to be used only in conjunction with static classes.

(Propably) Unexpected Usage

What might be unexpected when using accessors based on PrivateStaticAccessorBase is the fact that in contrast to the class to be tested, it is required to create an instance of the accessor.

Have a look at a short sample. Please note that the static class to be tested, StaticClass, is really static and no preprocessor directive is used.

/// <summary>
/// Class to be tested
/// </summary>
public static class StaticClass
{
  private static string StaticProperty { get; set; } = "My static Property";

  private static void StaticMethod()
  {
    Console.WriteLine("Static method invoked");
  }
}

/// <summary>
/// Accessor for testing <see cref="StaticClass"/>.
/// </summary>
internal class StaticClassAccessor : PrivateStaticAccessorBase
{
  internal StaticClassAccessor()
      : base(typeof(StaticClass))
  {
    // intentionally left blank
  }

  internal string StaticProperty
  {
    get { return (GetStaticPropertyValue()); }
    set { SetStaticPropertyValue(value); }
  }

  internal void StaticMethod()
  {
    InvokeStatic();
  }

// And here is the usage
StaticClassAccessor accessor = new StaticClassAccessor();

Console.WriteLine(accessor.StaticProperty);

accessor.StaticProperty = "New Value";

Console.WriteLine(accessor.StaticProperty);

accessor.StaticMethod();

// Output:
// My static Property
// New Value
// Static method invoked

Conclusion

I know I am repeating myself, but I am still convinced it is true: both the creation of the accessors of static classes and their usage by the test classes will be very comfortable and straightforward, having PrivateStaticAccessorBase in place.

Source Code

Here you can download the source code of the sample including PrivateStaticAccessorBase.

The code requires .NET 4.5 or higher. In case you would like to use it with previous .NET versions, please have a look at original post to see which changes are required in PrivateStaticAccessorBase.

AI Usage Note

This post was created using human intelligence only.

Links

Home-made Private Accessor for Visual Studio 2012+

Use Publicize.exe to Create Private Accessors for Visual Studio 2012+

Visual Studio error CS0718 ‘type’: static types cannot be used as type arguments

Stack Overflow: C# – static types cannot be used as type arguments

Static Constructors (C# Programming Guide)