简体   繁体   中英

C# How would I get a field through reflection once and make a delegate that returns its value for future use?

My use case : In my game I have an overlay that tracks any field with the '[TrackedField]' attribute. I want it to display the name of the variable, and the current value. Reflection being a bit of an expensive operation, I was looking for a way to retrieve the value once through reflection, and then make a delegate function, which doesn't use reflection, that returns the field's value. That way it can be called whenever I want to update that value on the overlay.

I actually don't know if what I'm asking is even possible, or if there would be a better way to retrieve this value. I've searched around for the past couple days, but all I was able to dig up was this related post . It will most likely be updated multiple times a second, so I'd like to avoid repeated use of reflection if I can.

Currently my code just gets every variable name (or a label defined in the attribute) and displays it with a dummy delegate that just reads "error":

MonoBehaviour[] sceneActive = GameObject.FindObjectsOfType<MonoBehaviour>();

foreach (MonoBehaviour mono in sceneActive)
{
    System.Type monoType = mono.GetType();

    // Retreive the fields from the mono instance
    FieldInfo[] objectFields = monoType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

    // search all fields and find the attribute [TrackedField]
    for (int i = 0; i < objectFields.Length; i++)
    {
        TrackedFieldAttribute attribute = Attribute.GetCustomAttribute(objectFields[i], typeof(TrackedFieldAttribute)) as TrackedFieldAttribute;

        // if we detect any attribute add it to the overlay
        if (attribute != null)
        {
            trackerBar.AddTab(attribute.label == null ? objectFields[i].Name : attribute.label, () => { return "error"; },attribute.color);
        }
    }
}

Here's an example of the '[TrackedField]' attribute in action:

[TrackedField]
private bool consoleOpen = false;
[TrackedField("MyLabel")]
private bool overlayShown = false;
[TrackedField("ColoredLabel", 50, 50, 255)]

It results in this on the overlay, if you were curious.

And if you were interested in what the attribute looked like:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class TrackedFieldAttribute : Attribute
{
    private string _label;
    private Color _color;

    public TrackedFieldAttribute()
    {
        _label = null;
        _color = default(Color);
    }

    public TrackedFieldAttribute(string label)
    {
        _label = label;
        _color = default(Color);
    }

    public TrackedFieldAttribute(float red = 0, float green = 0, float blue = 0)
    {
        _label = null;
        _color = new Color(red / 255f, green / 255f, blue / 255f);
    }

    public TrackedFieldAttribute(string label, float red = 0, float green = 0, float blue = 0)
    {
        _label = label;
        _color = new Color(red / 255f, green / 255f, blue / 255f);
    }

    public string label
    {
        get { return _label; }
        set { _label = value; }
    }

    public Color color
    {
        get { return _color; }
        set { _color = value; }
    }
}

I have never used Unity but in in classic .NET Framework this is possible:

public class FieldAccessor
{
    private delegate object FieldGetter(object instance);       
    private FieldGetter getter;

    public FieldAccessor(FieldInfo field)
    {
        ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance");

        MemberExpression member = Expression.Field(
            field.IsStatic ? null : Expression.Convert(instanceParameter, field.DeclaringType), // (TInstance)instance
            field);

        LambdaExpression lambda = Expression.Lambda<FieldGetter>(
            Expression.Convert(member, typeof(object)), // object return type
            instanceParameter); // instance (object)

        getter = (FieldGetter)lambda.Compile();
    }

    public object Get(object instance)
    {
        return getter(instance);
    }
}

Usage:

FieldInfo fi = typeof(MyType).GetField(...);

// creating the accessor is slow so cache this accessor instance:
var accessor = new FieldAccessor(fi);

// and then just use it like this:
var value = accessor.Get(myInstance);

I haven't used Unity as well. You may try to replace your delegate () => { return "error"; } () => { return "error"; } with

() => { return objectFields[i].GetValue(mono); }

Note that this delegate will return an object and just in case you should handle null values.

Also, it is still an expensive call but at least you cut on finding fields and enumerating attributes that are high expensive operations.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM