提问人:John Saunders 提问时间:7/9/2014 最后编辑:John Saunders 更新时间:7/24/2014 访问量:110
有什么方法可以用 LINQ 查询投影“原始加上一些更改”吗?
Any way to project "original plus a few changes" with a LINQ query?
问:
我有正在构建对象集合的代码。我试图减少我拨打的电话数量,所以我试图尽可能长时间地保留它。.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. 我认为这无关紧要,但集合最终会作为视图模型发送到 ASP.NET 视图。显然,可能必须在视图获得它之前调用它,但我希望这是唯一的一次。.ToList()
附注我应该说该类型大约有 30 个属性!用X
select new {
x.Property1,
x.Property2,
Property3 = setMe,
Property4 = setme,
// ...
}
将没有用,因为这将是另外 26 个属性。...
答:
取而代之的是:
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
});
我不相信有比这两个更好的方法。
评论
.Select
X
select
Property3
Property4
.*
你可以在你的类中创建一个私有方法,如下所示(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 方法中,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();
}
编辑:扩展方法怎么样,如下所示:
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);
或者在扩展方法中放置一个参数 string[],这样你就可以告诉方法哪些属性不能设置或设置取决于......
评论
ref MyClass c
c
PlaceValues
c = new MyClass()
ref
AsParallel
ForEach
除非您投影到的对象类型提供复制构造函数并且是可变的,否则没有优雅的方法可以做到这一点。
如果定义了复制构造函数并且对象是可变的,则可以执行以下操作:
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
});
这是我最终得到的:
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;
}
}
评论