任何投射方式"原创加上一些变化"使用LINQ查询?

时间:2014-07-08 22:13:22

标签: c# linq projection

我的代码正在构建一个对象集合。我正在尝试减少拨打的.ToList()次呼叫,因此我尽可能长时间地将其保留为IEnumerable<T>

除了需要设置为传递给调用方法的值的两个属性外,我几乎完成了它:

private IEnumerable<X> BuildCollection(int setMe){
    IEnumerable<Y> fromService = CallService();
    IEnumerable<X> mapped = Map(fromService);
    IEnumerable<X> filteredBySomething = FilterBySomething(mapped);

    IEnumerable<X> sorted = filteredBySomething
                                .OrderBy(x=>x.Property1)
                                .ThenBy(x=>x.Property2);

    // Here's the problem: return "sorted", but with each "Property3"
    //  and "Property4" set to "setMe". I don't want to do:

    var sortedList = sorted.ToList();
    sortedList.ForEach(s=>
    {
        s.Property3 = setMe;
        s.Property4 = setMe;
    };

    return sortedList;
}

如果可以在select中使用某种通配符,那么我可以执行以下操作:

return from f in filteredBySomething
    order by f.Property1, f.Property2
    select new {
        f.*,
        f.Property3 = setMe,
        f.Property4 = setMe
    };

也就是说,我想回流已排序的对象,但将Property3和Property4设置为传入的值。

有优雅的方法吗?

P.S。我认为这不重要,但最终会将该集合作为viewmodel发送到ASP.NET视图。显然,在View获取之前可能必须调用.ToList(),但我希望这是唯一的时间。

P.P.S。我应该说类型X有大约30个属性!使用

select new {
    x.Property1,
    x.Property2,
    Property3 = setMe,
    Property4 = setme,
    // ...
}

没有用,因为...将是另外26个属性。

4 个答案:

答案 0 :(得分:6)

而不是:

var sortedList = sorted.ToList();
sortedList.ForEach(s=>
{
    s.Property3 = setMe;
    s.Property4 = setMe;
};

这样做:

sorted = sorted.Select(x =>
{
    x.Property3 = setMe;
    x.Property4 = setMe;
    return x;
});

但是,如果您不想修改对象,则可以执行以下操作:

sorted = sorted.Select(x => new X()
{
    Property3 = setMe,
    Property4 = setMe,
    // set all other properties to what they were before
    // example: Property1 = x.Property1
});

我不相信有比这两个更好的方法。

答案 1 :(得分:1)

除非您要投射的对象类型提供了复制构造函数并且是可变的,否则,没有优雅的方法来执行此操作。

如果定义了复制构造函数且对象是可变的,则可以这样做:

var updatedSorted = sorted.Select(x => new X(x)
    {
        Property3 = setMe,
        Property4 = setMe,
    });

然而,匿名对象没有可访问的拷贝构造函数,也不可变,因此您必须完成将值复制到自己身上的工作。但是在一些辅助函数的帮助下,使用一些反射和一些好的ol可以减少它的痛苦。 LINQ。幸运的是,对于匿名对象,虽然我们在编译时无法访问这些类型,但这并不意味着我们无法在运行时创建新实例。

public static class AnonExtensions
{
    public static TSource SetValues<TSource, TValue>(
        this TSource source,
        Expression<Func<TSource, TValue>> setter)
    {
        var copierExpr = new Copier<TSource, TValue>().Rewrite(setter);
        var copier = copierExpr.Compile();
        return copier(source);
    }

    public static IEnumerable<TSource> UpdateValues<TSource, TValue>(
        this IEnumerable<TSource> source,
        Expression<Func<TSource, TValue>> setter)
    {
        var copierExpr = new Copier<TSource, TValue>().Rewrite(setter);
        var copier = copierExpr.Compile();
        return source.Select(copier);
    }

    public static IQueryable<TSource> UpdateValues<TSource, TValue>(
        this IQueryable<TSource> source,
        Expression<Func<TSource, TValue>> setter)
    {
        var copierExpr = new Copier<TSource, TValue>().Rewrite(setter);
        return source.Select(copierExpr);
    }

    private class Copier<TSource, TValue> : ExpressionVisitor
    {
        private readonly ParameterExpression param =
            Expression.Parameter(typeof(TSource));
        public Expression<Func<TSource, TSource>> Rewrite(
            Expression<Func<TSource, TValue>> setter)
        {
            var newExpr = new SubstitutionVisitor(
                setter.Parameters.Single(), param).Visit(setter.Body);
            var body = this.Visit(newExpr);
            return Expression.Lambda<Func<TSource, TSource>>(body, param);
        }

        protected override Expression VisitNew(NewExpression node)
        {
            var type = typeof(TSource);
            var ctor = type.GetConstructors().Single();
            var arguments = new List<Expression>();
            var members = new List<MemberInfo>();
            var propMap = GetPropertyMap(node);
            foreach (var prop in type.GetProperties())
            {
                Expression arg;
                if (!propMap.TryGetValue(prop.Name, out arg))
                    arg = Expression.Property(param, prop);
                arguments.Add(arg);
                members.Add(prop);
            }
            return Expression.New(ctor, arguments, members);
        }

        private Dictionary<string, Expression> GetPropertyMap(
            NewExpression node)
        {
            return node.Members.Zip(node.Arguments, (m, a) => new { m, a })
                .ToDictionary(x => x.m.Name, x => x.a);
        }
    }

    private class SubstitutionVisitor : ExpressionVisitor
    {
        private Expression oldValue, newValue;
        public SubstitutionVisitor(Expression oldValue, Expression newValue)
        { this.oldValue = oldValue; this.newValue = newValue; }

        public override Expression Visit(Expression node)
        {
            return node == oldValue ? newValue : base.Visit(node);
        }
    }
}

这将允许您这样做:

var updatedSorted = sorted.UpdateValues(x => new
    {
        Property3 = setMe, // the types here should match the corresponding 
        Property4 = setMe, // property types
    });

答案 2 :(得分:0)

您可以在类中创建一个私有方法(MyClass是您的课程类):

private void PlaceValues(MyClass c, int SetMe)
{
    PropertyDescriptorCollection col = TypeDescriptor.GetProperties(c);
    foreach (PropertyDescriptor prop in col)
    {
        if (prop.DisplayName != "Property1" & prop.DisplayName != "Property2")
        {
            prop.SetValue(c, SetMe);
        }
    }
}

然后在你的BuildCollection方法中你的过滤后的东西:

private IEnumerable<X> BuildCollection(int setMe){
    IEnumerable<Y> fromService = CallService();
    IEnumerable<X> mapped = Map(fromService);
    IEnumerable<X> filteredBySomething = FilterBySomething(mapped);

    IEnumerable<X> sorted = filteredBySomething
                                .OrderBy(x=>x.Property1)
                                .ThenBy(x=>x.Property2);

    // Here's the problem: return "sorted", but with each "Property3"
    //  and "Property4" set to "setMe". I don't want to do:

    sorted.AsParallel().ForAll(x => PlaceValues(x, SetMe));
    //Or without AsParallel(),using .ForEach() instead....

    return sorted.ToList();
}

编辑:扩展方法怎么样,如下所示:

public static void SetValues<TInn>(this IEnumerable<TInn> col, int ValueToApply)where TInn:MyClass
{
    PropertyDescriptorCollection pdCollection = TypeDescriptor.GetProperties(typeof(TInn));
    foreach (var item in col)
    {
        foreach (PropertyDescriptor des in pdCollection)
        {
            if (des.DisplayName != "Property1" & des.DisplayName != "Property2")
            {
                des.SetValue(item, ValueToApply);
            }
        }
    }
}

然后,不是我上面提到的,你这样做:

删除sorted.AsParallel().ForAll(x => PlaceValues(x, SetMe));

并放置sorted.SetValues(SetMe);

或者在扩展方法中放置一个params string [],这样你就可以告诉方法哪些属性不设置或设置依赖...

答案 3 :(得分:0)

这就是我最后的结果:

private IEnumerable<X> BuildCollection(int setMe){
    IEnumerable<Y> fromService = CallService();
    IEnumerable<X> mapped = Map(fromService);
    IEnumerable<X> filteredBySomething = FilterBySomething(mapped);

    IEnumerable<X> sorted = filteredBySomething
                                .OrderBy(x=>x.Property1)
                                .ThenBy(x=>x.Property2);
    // The method already returns IEnumerable<X> - make it an iterator    
    foreach (var x in sorted)
    {
        x.Property3 = setMe;
        x.Property4 = setMe;
        yield return x;
    }
}