Search code examples
c#static-methodshelper

Updating a static field by passing it as a method argument


My app will involve a lot of modifying of static fields. I'm wondering how to update the static fields from different classes by passing them as a method argument to a utility setter method.

internal static class Hero1
{
  public static bool awesomeAbility = false;
  public static bool anotherAwesomeAbility = false;
}

internal static class Hero2
{
  public static bool awesomeAbility = false;
  public static bool anotherAwesomeAbility = false;
}

internal static class Utils
{
  public static async void ToggleAbility(ability, bool value)
  {
    Logger.Write($@"{ability} = {value}");
    ability = value;
  }
}

// example usage....
ToggleAbility(Hero1.awesomeAbility, true);
ToggleAbility(Hero2.anotherAwesomeAbility, false);

Solution

  • It is possible to do this if you allow the caller to pass an expression tree (Expression<Func<bool>>), then you can get the field referenced by the expression tree as a FieldInfo.

    async void ToggleAbility(Expression<Func<bool>> field, bool value) {
        if (field.Body is MemberExpression memberExpression &&
            memberExpression.Member is FieldInfo fieldInfo) {
            
            // instead of field.Body, you can also use fieldInfo.Name for a simple name
            Logger.Write($@"{field.Body} = {fieldInfo.GetValue(null)}");
    
            // this sets the field's value
            fieldInfo.SetValue(null, value);
    
            // if you want to set it back asynchronously some time later...
            // await Task.Delay(...);
            // fieldInfo.SetValue(null, !value);
        } else {
            // the expression tree is not an access to a public static field
            // you could e.g. throw an exception 
        }
    }
    

    Usage:

    ToggleAbility(() => Hero1.awesomeAbility, true);
    

    Setting a field with SetValue is not very fast. If that is a problem for you, you can take a "field setter" Action<bool>

    async void ToggleAbility(Action<bool> setter, bool value) {
        setter(value)
        await Task.Delay(...);
        setter(!vakue)
    }
    
    ToggleAbility(x => Hero1.awesomeAbility = x, true);
    

    However, you cannot access the field name or its current value in ToggleAbility with this approach, unless you also pass them in as additional parameters:

    ToggleAbility(
        x => Hero1.awesomeAbility = x, 
        true,
        nameof(Hero1.awesomeAbility),
        Hero1.awesomeAbility);
    

    If your method doesn't need to be async, you can also pass the field in as a ref parameter, as lidqy's answer shows.