是否可以组合 GroupBy 和 Select 以获得正确命名的密钥?
Is it possible to combine a GroupBy and Select to get a proper named Key?
我非常喜欢 C# 中的 GroupBy LINQ 方法。
我不喜欢的一件事是密钥总是被称为 Key
。
当我在群组中循环时,Key
什么也没说。所以我必须查看上下文的其余部分以了解 Key 实际上是什么。
var grouped_by_competition = all_events
.GroupBy(e => e.Competition);
foreach ( var group in grouped_by_competition )
{
[...]
// ok what is Key for kind of object? I have to look at the outer loop to understand
group.Key.DoSomething();
}
当然你可以认为这是一件小事,因为你只需要查看外层循环。
在我看来,当您阅读它时,它仍然吃 'braincycles'。因此,如果有需要很少样板的好替代品,我想使用它。
有一些替代方案,但它们都添加了样板代码或依赖于注释——两者我都尽量省略。
选项 1 - 循环内的额外变量
var grouped_by_competition = all_events
.GroupBy(e => e.Competition)
foreach ( var group in grouped_by_competition )
{
var competition = group.Key;
[...]
competition.DoSomething();
}
此解决方案添加了不必要的样板代码。
选项 2 - 额外 Select
var grouped_by_competition = all_events
.GroupBy(e => e.Competition)
.Select(x => new { Competition = x.Key, Items = x } );
foreach ( var group in grouped_by_competition )
{
[...]
group.Competition.DoSomething();
}
此解决方案添加了不必要的样板代码。
选项 3 - 在 Key 的使用附近添加注释
我宁愿拥有富有表现力的代码,也不愿拥有过时的注释。
总结
理论上是否有可能设计出一种与匿名 class 功能类似的扩展方法?
var example = new { e.Competition };
example.Competition.DoSomething(); // property name deduced
所以我们得到:
var grouped_by_competition = all_events
.NamedGroupBy(e => e.Competition);
foreach ( var group in grouped_by_competition )
{
[...]
group.Competition.DoSomething();
}
Is it theoretically possible to design an extension method that somehow works similar like the anonymous class functionaliy?
不,因为名称必须在编译时出现,而您提议的扩展方法将依赖于 运行 时检查的 Expression
对象。
只有 才能按名称引用成员,前提是被引用的对象实际上具有该名称的成员。必须有一个实际的编译时名称供编译器使用。
(我在撒谎。当您使用 dynamic
关键字时,您告诉编译器将编译时操作推迟到 运行-time。这允许您逃避那些直到 运行 时间才解析具有特定成员名称的类型的技巧。这实际上是实现您既定目标的一种方法。但是,运行 在 运行 编译器]-time 可能很昂贵,并且放弃了编译时类型安全,这是 C# 和类似静态类型语言的标志性特征。dynamic
关键字在某些情况下是一个重要特征,但我不会说这个特定的用例证明了成本。)
就我个人而言,我不觉得 Key
这个名字有问题。在一个非常重要的方面,它非常 富有表现力:也就是说,它告诉我我正在处理一个分组的键。如果我想了解更多,其 类型 通常足以进一步详细说明,将鼠标悬停在其上可立即获得。
但是,因人而异。如果目标是让命名成员不同于 Key
,您的匿名类型投影是恕我直言,您将获得的最好结果。请注意,您可以通过使用允许您直接投影每个组的 GroupBy()
重载来提高简洁性,而不是使用 Select()
方法:
var grouped_by_competition = all_events
.GroupBy(e => e.Competition, (k, g) => new { Competition = k, Items = g });
所以,还有一件事:当我写下 "The only way for you to be able to refer to the member by name, would be if the object being referenced actually has a member of that name" 时,那里有点挥手。也就是说,真正重要的是编译器在编译时知道名称。
通常,这意味着确实存在一个类型,其命名成员具有该名称。但该规则有一个例外,最近添加到 C#,即使用 ValueTuple
的新元组语法。这样,您就可以隐式地给出一个名称,而无需引入实际的新类型。编译器只是在本地方法中跟踪它,并将其视为实际类型。
在此特定情况下,元组语法并没有真正改进匿名类型语法。它有点短,但不会短很多:
var grouped_by_competition = all_events
.GroupBy(e => e.Competition, (k, g) => (Competition: k, Items: g));
它确实具有使用 ValueTuple
class 的潜在性能优势;作为一种值类型,使用它可以显着减少 GC 开销。但并非所有情况都会从中受益。
我不确定哪种代码对你来说是干净的,但是在 Select 方法中使用动态类型怎么样?
var grouped_by_competition = all_events
.GroupBy(e => e.Competition)
.Select(x => {{
dynamic ret=new System.Dynamic.ExpandoObject();
ret.Competition=x.Key;
ret.Items=x;
return ret;
} );
foreach( var group in grouped_by_competition )
{
group.Competition.DoSomething();
}
如果你能接受进入dynamic
的痛苦世界,那么你就可以实施它。我不推荐它。
class DynamicGroup<TKey, TElement> : DynamicObject, IGrouping<TKey, TElement> {
private string keyname;
private IGrouping<TKey, TElement> items;
public DynamicGroup(IGrouping<TKey, TElement> pItems, string pKeyname) {
items = pItems;
keyname = pKeyname;
}
public IEnumerator<TElement> GetEnumerator() => items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public TKey Key { get => items.Key; }
public override bool TryGetMember(GetMemberBinder binder, out object result) {
if (binder.Name == keyname) {
result = items.Key;
return true;
}
else {
result = null;
return false;
}
}
}
static class Ext {
public static IEnumerable<dynamic> NamedGroupBy<TElement, TKey>(this IEnumerable<TElement> src, Expression<Func<TElement, TKey>> keySelector) {
var ksb = keySelector.Body as MemberExpression;
return src.GroupBy(keySelector.Compile()).Select(g => new DynamicGroup<TKey, TElement>(g, ksb.Member.Name));
}
}
我非常喜欢 C# 中的 GroupBy LINQ 方法。
我不喜欢的一件事是密钥总是被称为 Key
。
当我在群组中循环时,Key
什么也没说。所以我必须查看上下文的其余部分以了解 Key 实际上是什么。
var grouped_by_competition = all_events
.GroupBy(e => e.Competition);
foreach ( var group in grouped_by_competition )
{
[...]
// ok what is Key for kind of object? I have to look at the outer loop to understand
group.Key.DoSomething();
}
当然你可以认为这是一件小事,因为你只需要查看外层循环。
在我看来,当您阅读它时,它仍然吃 'braincycles'。因此,如果有需要很少样板的好替代品,我想使用它。
有一些替代方案,但它们都添加了样板代码或依赖于注释——两者我都尽量省略。
选项 1 - 循环内的额外变量
var grouped_by_competition = all_events
.GroupBy(e => e.Competition)
foreach ( var group in grouped_by_competition )
{
var competition = group.Key;
[...]
competition.DoSomething();
}
此解决方案添加了不必要的样板代码。
选项 2 - 额外 Select
var grouped_by_competition = all_events
.GroupBy(e => e.Competition)
.Select(x => new { Competition = x.Key, Items = x } );
foreach ( var group in grouped_by_competition )
{
[...]
group.Competition.DoSomething();
}
此解决方案添加了不必要的样板代码。
选项 3 - 在 Key 的使用附近添加注释
我宁愿拥有富有表现力的代码,也不愿拥有过时的注释。
总结
理论上是否有可能设计出一种与匿名 class 功能类似的扩展方法?
var example = new { e.Competition };
example.Competition.DoSomething(); // property name deduced
所以我们得到:
var grouped_by_competition = all_events
.NamedGroupBy(e => e.Competition);
foreach ( var group in grouped_by_competition )
{
[...]
group.Competition.DoSomething();
}
Is it theoretically possible to design an extension method that somehow works similar like the anonymous class functionaliy?
不,因为名称必须在编译时出现,而您提议的扩展方法将依赖于 运行 时检查的 Expression
对象。
只有 才能按名称引用成员,前提是被引用的对象实际上具有该名称的成员。必须有一个实际的编译时名称供编译器使用。
(我在撒谎。当您使用 dynamic
关键字时,您告诉编译器将编译时操作推迟到 运行-time。这允许您逃避那些直到 运行 时间才解析具有特定成员名称的类型的技巧。这实际上是实现您既定目标的一种方法。但是,运行 在 运行 编译器]-time 可能很昂贵,并且放弃了编译时类型安全,这是 C# 和类似静态类型语言的标志性特征。dynamic
关键字在某些情况下是一个重要特征,但我不会说这个特定的用例证明了成本。)
就我个人而言,我不觉得 Key
这个名字有问题。在一个非常重要的方面,它非常 富有表现力:也就是说,它告诉我我正在处理一个分组的键。如果我想了解更多,其 类型 通常足以进一步详细说明,将鼠标悬停在其上可立即获得。
但是,因人而异。如果目标是让命名成员不同于 Key
,您的匿名类型投影是恕我直言,您将获得的最好结果。请注意,您可以通过使用允许您直接投影每个组的 GroupBy()
重载来提高简洁性,而不是使用 Select()
方法:
var grouped_by_competition = all_events
.GroupBy(e => e.Competition, (k, g) => new { Competition = k, Items = g });
所以,还有一件事:当我写下 "The only way for you to be able to refer to the member by name, would be if the object being referenced actually has a member of that name" 时,那里有点挥手。也就是说,真正重要的是编译器在编译时知道名称。
通常,这意味着确实存在一个类型,其命名成员具有该名称。但该规则有一个例外,最近添加到 C#,即使用 ValueTuple
的新元组语法。这样,您就可以隐式地给出一个名称,而无需引入实际的新类型。编译器只是在本地方法中跟踪它,并将其视为实际类型。
在此特定情况下,元组语法并没有真正改进匿名类型语法。它有点短,但不会短很多:
var grouped_by_competition = all_events
.GroupBy(e => e.Competition, (k, g) => (Competition: k, Items: g));
它确实具有使用 ValueTuple
class 的潜在性能优势;作为一种值类型,使用它可以显着减少 GC 开销。但并非所有情况都会从中受益。
我不确定哪种代码对你来说是干净的,但是在 Select 方法中使用动态类型怎么样?
var grouped_by_competition = all_events
.GroupBy(e => e.Competition)
.Select(x => {{
dynamic ret=new System.Dynamic.ExpandoObject();
ret.Competition=x.Key;
ret.Items=x;
return ret;
} );
foreach( var group in grouped_by_competition )
{
group.Competition.DoSomething();
}
如果你能接受进入dynamic
的痛苦世界,那么你就可以实施它。我不推荐它。
class DynamicGroup<TKey, TElement> : DynamicObject, IGrouping<TKey, TElement> {
private string keyname;
private IGrouping<TKey, TElement> items;
public DynamicGroup(IGrouping<TKey, TElement> pItems, string pKeyname) {
items = pItems;
keyname = pKeyname;
}
public IEnumerator<TElement> GetEnumerator() => items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public TKey Key { get => items.Key; }
public override bool TryGetMember(GetMemberBinder binder, out object result) {
if (binder.Name == keyname) {
result = items.Key;
return true;
}
else {
result = null;
return false;
}
}
}
static class Ext {
public static IEnumerable<dynamic> NamedGroupBy<TElement, TKey>(this IEnumerable<TElement> src, Expression<Func<TElement, TKey>> keySelector) {
var ksb = keySelector.Body as MemberExpression;
return src.GroupBy(keySelector.Compile()).Select(g => new DynamicGroup<TKey, TElement>(g, ksb.Member.Name));
}
}