Przepraszam, jeśli jest to pytanie i odpowiedziałeś, szukałem, ale myślę, że nie znam słownictwa, aby znaleźć odpowiedź. Zbadane refleksję, ale to nie wydaje się być tutaj odpowiedzią? Jestem oczywiście nowicjuszem. Próbuję / dokonywać drobnych składek na mod dla nowej gry Battletech.

Mam to Dictionary i chciałbym używać kluczy do ustawiania właściwości, jak w foreach poniżej. Nie wiem, czy jest to w skompilowaniu lub wykonawczym, moim zgadywaniem jest czas kompilacyjny ...

Umieszczam * LIMB * AS PSEUDO-CODEK, aby wyobrażać sobie, że może działać. Właściwość mechDef.Head jest obiektem typu LocationLoadoutDef za pomocą jego właściwości CurrentInternalStructure oznacza float.

Mam nadzieję, że to ma sens!

Bardzo zobowiązany do każdej pomocy.

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());
}
c#
1
gnivler 2 czerwiec 2018, 02:41

5 odpowiedzi

Najlepsza odpowiedź

Możesz to zrobić z odbiciem, ale ....

Jest to całkiem łatwe do zrobienia z refleksją, a prawdopodobnie otrzymasz parę odpowiedzi tutaj, co pokazuje, jak ci, ale odkąd piszesz grę, zgaduję, że chcesz, abyś możliwa jest najlepsza wydajność, a odbicie nie zawsze da ci to.

Poniżej znajduje się rozwiązanie, które nie wymaga odbicia, ale nadal pozwala użyć żądanej struktury pętli. Po prostu wymaga odrobiny konfiguracji, gdy utworzysz obiekt, możesz uzyskać dostęp do swoich właściwości, jakby były w słowniku.

Rozwiązanie: Użyj słownika Delegatów, aby mapować właściwości

Najpierw musimy napisać klasę użytkową, która reprezentuje nieruchomość. Ponieważ właściwości mogą być różnymi typami, jest to klasa ogólna z argumentem typu.

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);
        }
    }
}

Ideą tej klasy jest to, że tworzysz go, aby reprezentować dowolną potrzebną właściwość, i zadzwoń do jego metod, aby przeczytać i ustawić nieruchomość. Klasa wie, jak czytać i ustawić nieruchomość, ponieważ informujesz o tym, jak go skonstruujesz, przekazując go krótki wyraz Lambda, który wykonuje pracę.

To narzędzie pozwoli ci umieścić wszystkie właściwości, które reprezentują kończyny do słownika. Wtedy możesz spojrzeć na ich ciąg, tak jak ustawienia. Tak więc na przykład twój mechaniczność może wyglądać tak:

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;
        }
    }
}

Tak, jest tam trochę konfiguracji, ale wszystko jest w jednym miejscu, a on tylko raz wykonuje, kiedy instancji MechDef. Teraz możesz uzyskać dostęp do wszystkich kończyn za pomocą ciągu:

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 do Przykład DotnetFiddle

2
John Wu 2 czerwiec 2018, 01:54

Możesz utworzyć dynamiczny, aby stworzyć swój własny dynamiczny słownik, Zobacz wyjaśnienie tutaj

Załóżmy, że chcesz zapewnić alternatywną składnię do uzyskiwania dostępu do wartości w słowniku, dzięki czemu zamiast pisania sampłodawskiego ["Tekst"] = "przykładowy tekst", możesz napisać sampledictionary.text = "przykładowy tekst".

Jest to przykład z tego samego artykułu MSDN powyżej:

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;
    }
}

I tak można korzystać z dynamicznictwa:

dynamic person = new DynamicDictionary();

// Adding new dynamic properties. The TrySetMember method is called.
person.FirstName = "Ellen";
person.LastName = "Adams";
1
Hooman Bahreini 2 czerwiec 2018, 00:15

Odbicie jest jednym ze sposobów na to. https://stackoverflow.com/a/1954663/83250 Właściwie odpowiada na to doskonale. Jednak restrukturyzowałbym Twoje dane, więc obiekt mechDef jest innym słownikiem, ale jeśli musisz zachować go jak twoje pytanie, pyta, to będzie działać:

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;
    }
}

Bądź świadomy, że refleksja jest bardzo kosztownym działaniem. Jeśli masz do czynienia z dużymi zestawami danych, będzie bardzo nieefektywne. Spójrz na https://stackoverflow.com/a/7478557/83250 dla przeglądzie wydajności.

Również mądry kod, wolę pozostać z dala od dynamicznego i całkowitego odbicia. Odbicie ma swoje korzyści, gdy trzeba uzyskać dostęp do atrybutu własności, a dynamiczna jest świetna, jeśli nie masz silnie wpisanego obiektu. Z tym, że C # jest silnie wpisanym językiem i powinien być traktowany jako taki, gdy jest to możliwe. Przez restrukturyzację mechDef jako obiekt Dictionary<string, Limb> lub coś podobnego, będziesz miał bardziej wydajną aplikację.

1
Mitchell Skurnik 2 czerwiec 2018, 00:37

Jeśli poprawnie rozumiem, masz coś takiego:

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; }
    ...
}

Możesz więc użyć refleksji uzyskania rodzaju obiektu i obiektu. Jest to przykład oparty na pseudokodzie:

// 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);
        }
    }
}
1
Cappe 2 czerwiec 2018, 00:43

Zawsze możesz tworzyć klasyczne i robocze {x0}} lub switch.
Lub Utwórz słownik z funkcją, aby zaktualizować poprawną właściwości

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
    };
}

Następnie w pętli wywołujesz poprawną akcję aktualizacji, stać się znacznie czyszczącym, aby użyć klas Ustawienia z korzyściami z silnych typów i kompilatorów, które zapobiegają dynamicznym błędom środowiskowym

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

foreach (var repair in updates) 
{
    repair.Update();
}
1
Fabio 2 czerwiec 2018, 06:42