Search code examples
c#reflection

Is there a way to find the parent object of a variable passed to a function?


I have a generic form filling system using C# and JavaScript. The data for a form comes from a C# class.

I am upgrading the system so it can do multi-page forms (without having to call back the C# to change pages). The class members now have attributes indicating which page each field of the form is shown on.

When the form is submitted from JavaScript to C#, it calls a C# function on the server which validates the input. If the input is invalid, it passes back an error message.

I currently use a Check function, which throws an exception if the checked assertion is not true. It would be great if the check function could tell the JavaScript which page the field with the error is on. I was hoping to be able to pass the field value into the check function, and it magically to be able to work out what class the field came from, look up the page number, and pass that back.

Example code:

public class PageAttribute : Attribute {
    public int Page;
    public PageAttribute(int page) {
        Page = page;
    }
}

public class Example {
    public int Id;
    [Page(1)]
    public string Name;
    [Page(1)]
    public string Address;
    [Page(2)]
    public int Calls;
    [Page(2)]
    public int Emails;
}

public class CheckException : ApplicationException {
    public int Page;
    public CheckException(string message, int page = 0) : base(message) {
        Page = page;
    }
}

void Check<T>(Object o, T item, Func<T, bool> validate, string message) {
    if (!validate(item)) {
        Type type = o.GetType();
        // Somehow magically find out which element in object o item comes from
        string name = "Calls";
        MemberInfo info = type.GetField(name) as MemberInfo;
        PageAttribute p = info.GetCustomAttribute(typeof(PageAttribute)) as PageAttribute;
        throw new CheckException(message, p == null ? 0 : p.Page);
    }
}

void Main() {
    Example e = new Example() { Id = 1, Name = "test", Calls = 3 };
    Check(e, e.Calls, x => x == 3, "Calls not equal to 3");
    Check(e, e.Calls, x => x == 2, "Calls not equal to 2");
}

Solution

  • Well, there's fundamentally no way of knowing where a particular value came from - the same value could be present in lots of places. (If you've just got the value 5, you presumably wouldn't want to find every occurrence of the number 5 in memory.)

    However, there's a better approach than reflection here here: CallerArgumentExpressionAttribute. That allows the compiler to provide "the expression used to provide an argument".

    Here's a really short example of that:

    using System.Runtime.CompilerServices;
    
    string x = "Hello";
    
    ShowValue(x.Length);
    
    void ShowValue(
        int value,
        [CallerArgumentExpression(nameof(value))] string? expression = null)
    {
        Console.WriteLine($"{expression} = {value}");
    }
    
    

    That prints x.Length = 5.

    So in your code, you'd need to:

    • Change Check to have an optional parameter like the expression one above
    • Find the last . in the expression
    • Get the field named by the remainder
    • Get the PageAttribute associated with that field

    Note that this is somewhat brittle - if someone refactors the call to Check to use a local variable for example, that would break the processing.

    I'd personally use typeof(T) instead of o.GetType(), and I'd recommend changing all your public fields into properties by the way, but those are changes somewhat unrelated to the core question