5

I have code that is building up a collection of objects. I'm trying to reduce the number of .ToList() calls I make, so I'm trying to keep it as IEnumerable<T> for as long as possible.

I have it almost finished except for two properties which need to be set to a value which is passed into the calling method:

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

If one could use sort of a wildcard in a select, then I could do something like:

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

That is, I would like to stream back the sorted objects, but with Property3 and Property4 set to the passed-in value.

Is there an elegant way to do this?

P.S. I don't think it matters, but the collection will eventually be sent to an ASP.NET view as a viewmodel. Clearly, .ToList() may have to be called before the View gets it, but I'd like that to be the only time.

P.P.S. I should have said that type X has about 30 properties! using

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

would not be useful, as the ... would be another 26 properties.

John Saunders
  • 160,644
  • 26
  • 247
  • 397

4 Answers4

6

Instead of this:

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

do this:

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

If, however, you don't want to modify the objects, then you can instead do the following:

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

I don't believe that there are any better ways than these two.

Jashaszun
  • 9,207
  • 3
  • 29
  • 57
  • 2
    This does have the ick-factor of mutating state in what should be a pure function in `.Select`. – Kirk Woll Jul 08 '14 at 22:26
  • @KirkWoll Well the only other solution would be to create new `X`s in the `select` with everything but `Property3` and `Property4` set to what they were before. I'll change my answer to show this. – Jashaszun Jul 08 '14 at 22:29
  • Right, but wouldn't that solution be precisely what John Saunders was trying to avoid -- i.e. the `.*` wish? I think the answer to John's question is that there is no elegant way to do what he wants. – Kirk Woll Jul 08 '14 at 22:32
1

Unless the object type you are projecting to provides a copy constructor and is mutable, then no, there is no elegant way to do this.

If a copy constructor was defined and the object was mutable, you could do this:

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

However anonymous objects do not have accessible copy constructors nor are mutable so you'll have to do the work of copying the values over yourself. But with the help of some helper functions, it can be made less painful using a bit of reflection and some good ol' LINQ. Fortunately with anonymous objects, although we don't have access to the types at compile time, it doesn't mean we can't create new instances at run time.

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

This will allow you to do this:

var updatedSorted = sorted.UpdateValues(x => new
    {
        Property3 = setMe, // the types here should match the corresponding 
        Property4 = setMe, // property types
    });
Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
0

You can create a private method in your class like this(MyClass is your class ofcourse):

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

Then in your BuildCollection method your filteredbysomething:

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

EDIT:How about an extension method,something like this:

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

Then instead of what i suggested above you do this:

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

And place sorted.SetValues(SetMe);

Or in the extension method place a params string[] so you tell the method what properties not to set or to set depending ...

terrybozzio
  • 4,424
  • 1
  • 19
  • 25
  • Couple of things: 1. why do you want `ref MyClass c`? You don't assign to `c` inside of `PlaceValues`. That is, there's no `c = new MyClass()` in there, so you don't need `ref`. 2. Introducing parallelism is not what I call elegant. 3. According to your comment, if I don't use `AsParallel`, then I should use `ForEach`, which is exactly what I want to avoid. – John Saunders Jul 08 '14 at 23:23
  • OPSS yes my bad i was trying other options before(about the ref),about the rest then i am sorry john,was just another option...aside from ForEach from the top of my head only calling jon skeet :) – terrybozzio Jul 08 '14 at 23:25
  • That will cause a multiple enumeration. Once to set the values then again for ToList. – John Saunders Jul 09 '14 at 01:00
0

Here's what I wound up with:

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;
    }
}
John Saunders
  • 160,644
  • 26
  • 247
  • 397