1

Sorry if this is asked and answered, I searched but think I don't know the vocabulary to find the answer. Researched reflection but that doesn't seem to be the answer here? I'm a novice obviously. I'm trying/making minor contributions to a mod for the new Battletech game.

I've got this Dictionary and would like to use its keys to set properties as in the foreach below. I don't know if this is at compile or runtime, my guess is compile time...

I put *limb* in as pseudo-code for how I'm imagining it might work. The property mechDef.Head is an object of type LocationLoadoutDef with its property CurrentInternalStructure being float.

Hope that makes sense!

Much obliged for any assistance.

public class Settings {
    public readonly Dictionary<string, bool> LimbRepair = new Dictionary<string, bool> {
        { "Head", false },
        { "LeftArm", false },
        { "RightArm", false },
        { "CenterTorso", false },
        { "LeftTorso", false },
        { "RightTorso", false },
        { "LeftLeg", false },
        { "RightLeg", false },
    };
}

MechDef mechDef = new MechDef
       (__instance.DataManager.MechDefs.Get(id), __instance.GenerateSimGameUID());

foreach (string limb in settings.LimbRepair.Keys) {
    if (!settings.LimbRepair[limb]) {                          
        mechDef.*limb*.CurrentInternalStructure = Math.Max
                    (1f, mechDef.*limb*.CurrentInternalStructure * (float)rng.NextDouble());
}
gnivler
  • 100
  • 1
  • 13
  • 1
    Reflection is the correct keyword for what you want to do, **but** it would be much better to refactor `mechDef` to be a dictionary instead of trying to access its properties via reflection. – Blorgbeard Jun 02 '18 at 00:00
  • Thanks for the answer! I believe MechDef is in a provided game assembly, is it possible to refactor still? – gnivler Jun 02 '18 at 00:02
  • No, I was assuming you had access to the source of `MechDef`. Does this library have public API docs? – Blorgbeard Jun 02 '18 at 00:05
  • I'll check but I would say not, tried looking for something along those lines yesterday. Just found this https://stackoverflow.com/questions/1196991/get-property-value-from-string-using-reflection-in-c-sharp#1197004 which is perhaps the less ideal solution? It's over my head but I'll work on it if you think it could succeed – gnivler Jun 02 '18 at 00:07
  • 1
    That will work, you should add a cast to `(LocationLoadoutDef)` and return that type instead of `object`, though. Given there's only 8 possible limbs, you could also simply write your own function that accepts a `string` and a `MechDef` and returns the appropriate property using an `if-else` chain. – Blorgbeard Jun 02 '18 at 00:11
  • Right on, thanks very much, I will check it out. Ironically the current code uses that if structure and I thought I might be able to streamline it. – gnivler Jun 02 '18 at 00:13
  • 1
    Yeah, although you can write more generic code with reflection, it's not often pretty, and often slower. – Blorgbeard Jun 02 '18 at 00:16
  • Very good to know – gnivler Jun 02 '18 at 00:17

5 Answers5

2

You can do it with Reflection, but....

This is quite easy to do with Reflection, and you'll probably get a couple answers on here that show you how, but since you are writing a game, I'm guessing you want the best performance possible, and Reflection isn't always going to give you that.

Below is a solution that requires no reflection but still allows you to use the loop structure you want. It just requires a little bit of setup when you create the object, then you can access your properties as if they were in a dictionary.

Solution: Use a dictionary of delegates to map the properties

First we need to write a utility class that represents a property. Since properties can be different types, this is a generic class with a type argument.

class PropertyWrapper<T>
{
    private readonly Func<T> _getter;
    private readonly Action<T> _setter;

    public PropertyWrapper(Func<T> getter, Action<T> setter)
    {
        _getter = getter;
        _setter = setter;
    }

    public T Value
    {
        get
        {
            return _getter();
        }
        set
        {
            _setter(value);
        }
    }
}

The idea behind this class is that you create it to represent any property you want, and call its methods to read and set the property. The class knows how to read and set the property because you tell it how, when you construct it, by passing it a short lambda expression that does the work.

This utility will allow you to put all the properties that represent limbs into a dictionary. Then you can look them up by string, just like your settings. So for example your MechDefinition might look like this:

class MechDef
{
    public Limb Head        { get; set; }
    public Limb LeftArm     { get; set; }
    public Limb RightArm    { get; set; }
    public Limb LeftTorso   { get; set; }
    public Limb RightTorso  { get; set; }
    public Limb CenterTorso { get; set; }
    public Limb RightLeg    { get; set; }
    public Limb LeftLeg     { get; set; }

    private readonly Dictionary<string, PropertyWrapper<Limb>> Properties;

    public MechDef()
    {
        Properties = new Dictionary<string, PropertyWrapper<Limb>>
        {
            {"Head",       new PropertyWrapper<Limb>( () => Head,        v => Head = v )       },
            {"LeftArm",    new PropertyWrapper<Limb>( () => LeftArm,     v => LeftArm = v )    },
            {"RightArm",   new PropertyWrapper<Limb>( () => RightArm,    v => RightArm = v )   },
            {"CenterTorso",new PropertyWrapper<Limb>( () => CenterTorso, v => CenterTorso = v )},
            {"RightTorso", new PropertyWrapper<Limb>( () => RightTorso,  v => RightTorso = v ) },
            {"LeftTorso",  new PropertyWrapper<Limb>( () => LeftTorso,   v => LeftTorso = v )  },
            {"RightLeg",   new PropertyWrapper<Limb>( () => RightLeg,    v => RightLeg = v )   },
            {"LeftLeg",    new PropertyWrapper<Limb>( () => LeftLeg,     v => LeftLeg = v )    }
        };

        foreach (var property in Properties.Values) property.Value = new Limb();
    }

    public Limb this[string name]
    {
        get
        {
            return Properties[name].Value;
        }
        set
        {
            Properties[name].Value = value;
        }
    }
}

Yes, there is a bit of setup there, but it's all in one place, and it only executes once, when you instantiate the MechDef. Now you can access all of the limbs by string:

foreach (var pair in settings.LimbRepair)
{
    if (pair.Value != false) continue;
    var limb = mechDef[pair.Key];
    limb.CurrentInternalStructure = Math.Max
        (
            1.0F, 
            limb.CurrentInternalStructure * (float)rng.NextDouble()
        );
}

Link to DotNetFiddle example

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Hmm my last comment MIA. Basically - Wow, and thanks for taking the time to explain and write the examples. I'm not writing a game though just a contributor to a mod and the game provides the assembly with MechDef so I think that means I can't change it. Does that weigh on this solution? – gnivler Jun 02 '18 at 08:05
1

You can create a DynamicObject to create your own dynamic Dictionary, See the explanation here

Assume that you want to provide alternative syntax for accessing values in a dictionary, so that instead of writing sampleDictionary["Text"] = "Sample text", you can write sampleDictionary.Text = "Sample text".

This is the example from the same MSDN article above:

public class DynamicDictionary : DynamicObject
{
    // The inner dictionary
    Dictionary<string, object> dictionary = new Dictionary<string, object>();

    public int Count
    {
        get { return dictionary.Count; }
    }

    // If you try to get a value of a property not defined 
    // in the class, this method is called.
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        // Converting the property name to lowercase so 
        // that property names become case-insensitive.
        string name = binder.Name.ToLower();

        // If the property name is found in a dictionary, set the result parameter
        // to the property value and return true. Otherwise, return false.
        return dictionary.TryGetValue(name, out result);
    }

    // If you try to set a value of a property that is not 
    // defined in the class, this method is called.
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        // Converting the property name to lowercase so that 
        // property names become case-insensitive.
        dictionary[binder.Name.ToLower()] = value;

        // You can always add a value to a dictionary, so this method always returns true.
        return true;
    }
}

And this is how you can use your DynamicDictionary:

dynamic person = new DynamicDictionary();

// Adding new dynamic properties. The TrySetMember method is called.
person.FirstName = "Ellen";
person.LastName = "Adams";
Hooman Bahreini
  • 14,480
  • 11
  • 70
  • 137
  • 1
    Wow. Thank you for the answer. It's way over my head, I couldn't even be sure it's the answer without considerable effort to implement the concept. That's my problem though! Marking as the answer with thanks also to @blorgbeard for their insight in the question comments – gnivler Jun 02 '18 at 00:16
  • @gnivler, This is just an example to demonstrates how to access a dictionary property by name (dynamic property). – Hooman Bahreini Jun 02 '18 at 00:26
  • Ok understood thanks, that also gives me a reading topic! Also apologies for unchecking as answer, too much for me to judge. Your time is much appreciated – gnivler Jun 02 '18 at 00:27
1

Reflection is one way to get at it. https://stackoverflow.com/a/1954663/83250 actually answers this perfectly. I would however restructure your data so the mechDef object is another dictionary but if you must keep it like your question asks, this will work:

void Main()
{
    Dictionary<string, bool> limbRepair = new Dictionary<string, bool>
    {
        { "Head", false },
        { "LeftArm", false },
        { "RightArm", false },
        // Etc.
    };

    MechDefinition mechDef = new MechDefinition();
    List<Limb> limbs = new List<Limb>();
    foreach (KeyValuePair<string, bool> limbsToRepair in limbRepair.Where(x => !x.Value))
    {
        Limb limb = mechDef.GetPropValue<Limb>(limbsToRepair.Key);
        limb.CurrentInternalStructure = 9001;
    }
}

public class MechDefinition
{
    public MechDefinition()
    {
        Head = new Limb
        {
            Id = Guid.NewGuid(),
            DateAdded = DateTime.Parse("2018-01-01"),
            Name = "Main Head",
            CurrentInternalStructure = 8675309
        };
    }
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int CurrentInternalStructure { get; set; }
    public Limb Head { get; set; } = new Limb();
    public Limb LeftArm { get; set; } = new Limb();
    public Limb RightArm { get; set; } = new Limb();
    // etc...
}

public class Limb
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public DateTime DateAdded { get; set; }
    public int CurrentInternalStructure { get; set; }
    public bool IsDisabled { get; set; }
}

public static class ReflectionHelpers
{
    public static object GetPropValue(this object obj, string name)
    {
        foreach (string part in name.Split('.'))
        {
            if (obj == null) { return null; }

            Type type = obj.GetType();
            PropertyInfo info = type.GetProperty(part);
            if (info == null) { return null; }

            obj = info.GetValue(obj, null);
        }
        return obj;
    }

    public static T GetPropValue<T>(this object obj, string name)
    {
        object retval = GetPropValue(obj, name);
        if (retval == null) { return default(T); }

        // throws InvalidCastException if types are incompatible
        return (T)retval;
    }
}

Be aware that reflection is a very costly operation. If you are dealing with large sets of data, it will be very inefficient. Take a look at https://stackoverflow.com/a/7478557/83250 for a performance overview.

Also code-wise, I prefer to stay away from dynamic and reflection altogether. Reflection has its perks when you need to access a property attribute and dynamic is great if you don't have a strongly typed object. With that said, C# is a strongly typed language and should be treated as such whenever possible. By restructuring your mechDef as a Dictionary<string, Limb> object or something similar you will have a more efficient application.

Mitchell Skurnik
  • 1,419
  • 4
  • 25
  • 37
  • just pasting from my comment above because it's the same sentiment: thanks very much for the answer. I hope people who know can vote these properly so I can mark the appropriate answer, I wasn't expecting so many responses and approaches and have to try them all out – gnivler Jun 02 '18 at 00:26
  • Noted, and thanks. In this case it's quite simple so the complexity and flexibility is likely major overkill. I can't change mechDef unfortunately, it's just part of the game.. well, AFAIK, which isn't saying much – gnivler Jun 02 '18 at 00:32
  • Note, that I removed the `SetPropValue` method as it is not needed. – Mitchell Skurnik Jun 02 '18 at 00:38
1

You can always create classic and working if .. else or switch.
Or create dictionary with function to update correct property

public class Repair
{
    public bool Active { get; set; }
    public Action<MechDef> Update { get; set; }
}

public class Settings 
{
    public readonly Dictionary<string, Repair> LimbRepair = 
        new Dictionary<string, bool> {
        { 
            "Head", 
            new Repair { Active = false, mechDef => mechDef.Head.CurrentInternalStructure = yourFunctionForHead }
        },
        { 
            "LeftArm", 
            new Repair { Active = false, mechDef => mechDef.LeftArm.CurrentInternalStructure = yourFunctionForLeftArm }
        },
        // ... and so on
    };
}

Then in the loop you will call correct update action, become much cleaner to use settings class with benefits of strong types and compiler help which prevent dynamic runtime errors

var updates = settings.LimbRepair.Where(pair => pair.Value.Active == false)
                                 .Select(pair => pair.Value);

foreach (var repair in updates) 
{
    repair.Update();
}
Fabio
  • 31,528
  • 4
  • 33
  • 72
  • thanks very much for the answer. I hope people who know can vote these properly so I can mark the appropriate answer, I wasn't expecting so many responses and approaches and have to try them all out – gnivler Jun 02 '18 at 00:26
1

If I understand correctly, You have something like this:

class LocationLoadoutDef
{
    public LocationLoadoutDef()
    {
        Head = new Prop();
        LeftArm = new Prop();
        RightArm = new Prop();
        CenterTorso = new Prop();
        LeftTorso = new Prop();
        RightTorso = new Prop();
        LeftLeg = new Prop();
        RightLeg = new Prop();
    }

    public Prop Head { get; set; }
    public Prop LeftArm { get; set; }
    public Prop RightArm { get; set; }
    public Prop CenterTorso { get; set; }
    public Prop LeftTorso { get; set; }
    public Prop RightTorso { get; set; }
    public Prop LeftLeg { get; set; }
    public Prop RightLeg { get; set; }
    ...
}

class Prop
{
    public float CurrentInternalStructure { get; set; }
    ...
}

So you can use reflection getting the type of the object and the property. This is an example based on your pseudocode:

// your instance of LocationLoadoutDef
var mechDef = new LocationLoadoutDef();

//For reflection you need obtain the type
Type mechType = mechDef.GetType();

// loop your Dictionary
foreach (string limb in LimbRepair.Keys)
{
    // If the property is false in the dictionary and the type has a property with that name
    if (!LimbRepair[limb] && mechType.GetProperties().Any(p => p.Name == limb))
    {
        // Obtain the instance of the property
        var property = mechType.GetProperty(limb).GetValue(mechDef) ;

        // Get the property type 
        Type propertyType = property.GetType();

        // If the property has a property CurrentInternalStructure
        if (propertyType.GetProperties().Any(p => p.Name == "CurrentInternalStructure"))
        {
            // Obtain the current value for CurrentInternalStructure
            var currentValue = propertyType.GetProperty("CurrentInternalStructure").GetValue(property);

            // calculate the new value (I don't know what is rng)
            var newValue = 1f ; //Math.Max(1f, (float)currentValue * (float)rng.NextDouble());

            // set de value in the property
            propertyType.GetProperty("CurrentInternalStructure").SetValue(property, newValue);
        }
    }
}
Cappe
  • 21
  • 5
  • Thanks for the answer. You're supposing correctly, but it's from a game so I can only see the metadata. It's pretty opaque to me.`public LocationLoadoutDef(ChassisLocations Location, float CurrentArmor = 0, float CurrentRearArmor = 0, float CurrentInternalStructure = 0, float AssignedArmor = 0, float AssignedRearArmor = 0, LocationDamageLevel DamageLevel = LocationDamageLevel.Functional);` – gnivler Jun 02 '18 at 00:54
  • Based on my total lack of knowledge or experience with reflection plus the fact that it seems a poor approach to my problem, I think I'm going to go with some if/else to keep it in my wheel house while I go through all these answers. – gnivler Jun 02 '18 at 00:55