Метод возвращает коллекцию объектов, отфильтрованных строк


У меня есть Vehicle объект, который имеет несколько свойств, таких как Make, Model, Priceи т. д.

У меня тоже есть VehicleCollection объект, который вытекает из List<Vehicle в которой есть несколько специальных методов, одним из которых является следующее...

class VehicleCollection : List<Vehicle>
{
    public VehicleCollection GetVehicles(string _searchTerm = "")
    {
        // return the entire collection if no search term is provided
        if (_searchTerm.Length == 0)
            return this;

        var matchingVehicles = new VehicleCollection();
        foreach(var vehicle in this) // loop through all vehicles
        {
            PropertyInfo[] propInfos = vehicle.GetType().GetProperties(); // all properties of the current vehicle
            for (int i = 0; i < propInfos.Length; i++)
            {
                var propVal = propInfos[i].GetValue(vehicle); // the value of the current property of the current vehicle

                // check aganst value of a strings or the values within a string array
                // ignores case
                if (propVal is string propertyValueStr && propertyValueStr.IndexOf(_searchTerm, StringComparison.CurrentCultureIgnoreCase) >= 0
                    || propVal is string[] propValueArr && propValueArr.Any(x => x.IndexOf(_searchTerm, StringComparison.CurrentCultureIgnoreCase) >= 0))
                {
                    matchingVehicles.Add(vehicle);
                }
            }
        }

        return matchingVehicles;
    }
}

Возвращает коллекцию автомобилей, из которых каждый автомобиль должен иметь свойство, значение которого содержит поставила _searchTerm параметр.

Это лучший способ, чтобы сделать это? Это может быть достигнуто с помощью LINQ вообще? Это может быть сделано без отражения?

Редактировать: По просьбе комментария, вот это класс автомобиля, с несколько излишней опущены методов.

public class Vehicle
{
    public string ID { get; private set; }
    public string Make { get; private set; }
    public string Model { get; private set; }
    internal decimal Value { get; private set; }
    public string Price { get { return string.Format("{0:C}", Value); } }
    public VehicleColourEnum Colour { get; private set; }
    internal StatusEnum Status { get; private set; }
    public string State
    {
        get 
        {
            switch (Status)
            {
                case StatusEnum.ComingSoon:
                    return "Coming Soon";
                case StatusEnum.HasDeposit:
                    return "Has Deposit";
                case StatusEnum.InStock:
                    return "In Stock";
                case StatusEnum.Sold:
                    return "Sold";
                case StatusEnum.Unknown:
                default:
                    return "Unknown";
            }
        }
    }

    public Vehicle(string _make, string _model, decimal _value, VehicleColourEnum _colour = VehicleColourEnum.None, StatusEnum _status = StatusEnum.Unknown)
    {
        Make = _make;
        Model = _model;
        Value = _value;
        Colour = _colour;
        Status = _status;
        ID = Guid.NewGuid().ToString("N");
    }
}


944
3
задан 20 марта 2018 в 10:03 Источник Поделиться
Комментарии
3 ответа

Я буду игнорировать ваши правила именования/и т. д., Как я знаю, это не то, что вы спрашиваете.

Поскольку объект VehicleCollection уже наследует от IEnumerable по списку<> наследованию, вы можете запросить коллекцию как список через 'это' ключевое слово.

Это означает, что вы могли бы написать свой метод поиска, как:

public VehicleCollection GetVehicles(string searchTerm = "")
{
// Null and white-space check for sanity
if(string.IsNullOrWhiteSpace(searchTerm))
// We're assuming if the search term is bad, we return everything.
// This isn't standard practice - usually we return nothing
return this;

var matches = new VehicleCollection();
matches.AddRange(
this.Where(v=>v.ID.Equals(searchTerm) ||
v.Make.Equals(searchTerm) ||
v.Model.Equals(searchTerm)
// I personally prefer to be explicit in what I'm selecting, but it's your choice
).Select(v=>v));

// inline IF because I'm lazy
// again, we're returning everything if there are no matches,
// which isn't standard practice.
return matches.Any()? matches: this;
}

Надеюсь, что помогает!

4
ответ дан 20 марта 2018 в 01:03 Источник Поделиться

Если ты найдешь подходящую машину, вы добавить его. Затем вы продолжите проверку на тот же автомобиль, вместо того, чтобы вырваться из внутреннего цикла, чтобы получить следующий автомобиль.

Рефлексия-это вяло. Я хотел бы предложить, имея Vehicle класс определить, если свои матчи в строку поиска. Это означало бы, имея такой способ, как:

public bool IsMatch(string value) ...

Тогда ваш цикл за всех средств становится гораздо проще и быстрее.

4
ответ дан 20 марта 2018 в 01:03 Источник Поделиться

Хотя, возможно, перебор, использование рефлексии и запрос LINQ, которая заставила меня задуматься о динамическом создании выражения.

Я считаю, что GetVehicles метод сбора могут быть делегированы, но используя простой метод расширения, которые следовали ваши первоначальные попытки фильтрующих свойств я придумал

public static class ExpressionBuilder {

public static IEnumerable<T> Match<T>(this IEnumerable<T> collection, string filter = null) where T : class {
// return the entire collection if no search term is provided
if (string.IsNullOrWhiteSpace(filter))
return collection;

var lambda = ExpressionBuilder.IsMatch<T>(filter);

var matches = collection.Where(lambda.Compile());

return matches;
}

static Expression<Func<T, bool>> IsMatch<T>(string filter) where T : class {
var type = typeof(T);
var properties = type.GetProperties();

// (T _) => ...
var param = Expression.Parameter(type, "_");
//filter
var filterConstant = Expression.Constant(filter);
// null
var nullString = Expression.Constant(null, typeof(string));

Expression body = null;

foreach (var propertyInfo in properties) {
var propertyType = propertyInfo.PropertyType;
if (propertyType == typeof(string)) {
// _.Property
var property = Expression.Property(param, propertyInfo);
// _.Property != null
var notNull = Expression.NotEqual(property, nullString);
// _.Property.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase)
var method = Expression.Call(
property,
propertyType.GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) }),
filterConstant,
Expression.Constant(StringComparison.CurrentCultureIgnoreCase)
);
// _.Property.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase) >= 0
var contains = Expression.GreaterThanOrEqual(method, Expression.Constant(0));
// _.Property != null && _.Property.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase) >= 0
var condition = Expression.AndAlso(notNull, contains);

if (body == null) {
body = condition;
} else {
body = Expression.Or(body, condition);
}
} else if (propertyType.IsValueType) {
// _.Property
var property = Expression.Property(param, propertyInfo);
// _.Property.ToString()
var method = Expression.Call(
property,
propertyType.GetMethod("ToString", new Type[0])
);
// _.Property.ToString().IndexOf(filter, StringComparison.CurrentCultureIgnoreCase)
method = Expression.Call(
method,
method.Method.ReturnType.GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) }),
filterConstant,
Expression.Constant(StringComparison.CurrentCultureIgnoreCase)
);
// _.Property.ToString().IndexOf(filter, StringComparison.CurrentCultureIgnoreCase) >= 0
var contains = Expression.GreaterThanOrEqual(method, Expression.Constant(0));
var condition = contains;

if (body == null) {
body = condition;
} else {
body = Expression.Or(body, condition);
}
}
}

var lambda = Expression.Lambda<Func<T, bool>>(body, param);

return lambda;
}
}

Который строит дерево выражения, которое базируется на тип объекта, который будет использоваться с расширение помощью LINQ.

Например

public class VehicleCollection : List<Vehicle> {

public VehicleCollection()
: base() {

}

public VehicleCollection(IEnumerable<Vehicle> collection)
: base(collection) {

}

public VehicleCollection GetVehicles(string _searchTerm = "") {
// return the entire collection if no search term is provided
if (string.IsNullOrWhiteSpace(_searchTerm))
return this;

var matches = this.Match(_searchTerm); //<-- calling extension method

var matchingVehicles = new VehicleCollection(matches);

return matchingVehicles;
}
}

Метод расширения является первой попыткой и есть возможности для совершенствования в плане того, что предикат может обработчика. Однако этого должно быть достаточно, чтобы вы начали.

3
ответ дан 20 марта 2018 в 03:03 Источник Поделиться