Как найти поле класса по строке - PullRequest
1 голос
/ 23 июня 2019

У меня есть функция, где я ищу поле на основе ввода строки. И я хочу проверить, если поле не равно нулю, и вернуть строку, если оно не равно нулю. Пример кода показывает жестко закодированный оператор else-if (который на самом деле длиннее). Мне интересно, есть ли более простой способ сделать это. Моя интуиция говорит отражение, но я не знаю, как это сделать или что искать.

public class Spell
{
    public string name { get; set; }
    public string description { get; set; }
    public string sor { get; set; }
    public string wiz { get; set; }
    public string cleric { get; set; }
}



public IEnumerable<Spell> GetSpellsForClass(string classname)
{
    if(classname =="sor")
        return _context.Spells.Where(x=> !string.IsNullOrEmpty(x.sor));
    else if (classname == "wiz")
        return _context.Spells.Where(x => !string.IsNullOrEmpty(x.wiz));
    else if(classname == "cleric")
        return _context.Spells.Where(x => !string.IsNullOrEmpty(x.cleric));
}

Я ожидаю ввода 'sor', 'wiz' или 'cleric' и ожидаю, что функция найдет соответствующее поле, проверит, является ли оно пустым, и вернет весь объект.

Ответы [ 4 ]

0 голосов
/ 24 июня 2019

Я думаю, что лучшее, что вы можете сделать, это Dictionary для сопоставления имен классов с лямбда-методами, которые обращаются к соответствующему члену. Это будет не так эффективно, как ваш if / else (или лучше switch / case) метод. ПРИМЕЧАНИЕ. Если ваш switch достаточно длинный, компилятор может сгенерировать хеш-таблицу случаев для поиска места ветвления.

Мне напомнили, что это запрос LINQ to EF, и я обновил его для создания соответствующего Expression.

Дано:

Dictionary<string, Expression<Func<Spell, string>>> AccessSpellClass = new Dictionary<string, Expression<Func<Spell, string>>>() {
    { "sor", s => s.sor },
    { "wiz", s => s.wiz },
    { "cleric", s => s.cleric },
};

И ExpressionVisitor для замены Expression s:

public static class ExpressionExt {
    /// <summary>
    /// Replaces an Expression (reference Equals) with another Expression
    /// </summary>
    /// <param name="orig">The original Expression.</param>
    /// <param name="from">The from Expression.</param>
    /// <param name="to">The to Expression.</param>
    /// <returns>Expression with all occurrences of from replaced with to</returns>
    public static Expression Replace(this Expression orig, Expression from, Expression to) => new ReplaceVisitor(from, to).Visit(orig);
}

/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
    readonly Expression from;
    readonly Expression to;

    public ReplaceVisitor(Expression from, Expression to) {
        this.from = from;
        this.to = to;
    }

    public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}

Вы можете использовать его в своем методе:

public IEnumerable<Spell> GetSpellsForClass(string classname) {
    if (AccessSpellClass.TryGetValue(classname, out var accessExpr)) {
        Expression<Func<string,bool>> testExpr = x => !String.IsNullOrEmpty(x);
        var newTestBody = testExpr.Body.Replace(testExpr.Parameters[0], accessExpr.Body);
        var newTestExpr = Expression.Lambda<Func<Spell,bool>>(newTestBody, accessExpr.Parameters[0]);

        return _context.Spells.Where(newTestExpr);
    }
    else
        return Enumerable.Empty<Spell>();
}

Лучше всего, если вы создадите Attribute, и тогда вы можете создать Dictionary во время выполнения в начале вашей программы:

public class UserClassAttribute : Attribute {
}

public class Spell {
    public string name { get; set; }
    public string description { get; set; }
    [UserClass]
    public string sor { get; set; }
    [UserClass]
    public string wiz { get; set; }
    [UserClass]
    public string cleric { get; set; }
}

void BuildAccessSpellClass() {
    var members = typeof(Spell).GetProperties();
    AccessSpellClass = new Dictionary<string, Func<Spell, string>>();
    foreach (var p in members) {
        if (p.GetCustomAttribute(typeof(UserClassAttribute)) != null) {
            var className = p.Name;
            var parmS = Expression.Parameter(typeof(Spell), "s");
            var body = Expression.MakeMemberAccess(parmS, p);
            var accessExpr = Expression.Lambda<Func<Spell,string>>(body, parmS);
            AccessSpellClass.Add(className, accessExpr);
        }
    }
}

ПРИМЕЧАНИЕ. Если вы не можете использовать пользовательский атрибут, вы можете изменить метод так, чтобы он брал список имен свойств и создавал словарные записи для этих свойств.

0 голосов
/ 23 июня 2019

Вот ответ, основанный на размышлениях, с использованием метода расширения, который, как мне кажется, вы действительно хотите.

public static bool TryIsPropertyNull(this object obj, string PropName, out bool isNull)
{
    isNull = false;
    var prop = obj.GetType().GetProperty(PropName);
    if (prop == null)
        return false;
    isNull = prop.GetValue(obj) == null;
    return true;
}

Я сделал это как "попытку", чтобы объяснить ситуации, когдаимя свойства нет.Надеемся, это даст вам несколько указателей в правильном направлении

0 голосов
/ 23 июня 2019

Разве не чище использовать словарь для типов заклинаний?Вы можете иметь таблицу классов, которые связаны с заклинанием, или какой-то другой тег.Тогда у вас может быть свойство в вашем классе - скажем, List of CharacterClasses, чтобы вы могли обращаться к идентификаторам.Это также повысит вашу производительность, устраняя отражения.

0 голосов
/ 23 июня 2019

Вы могли бы так это:

_context.Spells.Where(s => typeof(Spell).GetProperty(classname).GetValue(s) != null);

Таким образом, вы просто ищете имя класса (я бы предложил использовать propertyName вместо имени переменной), и у вас нет оператора if else.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...