如何正确键入声明为 class 属性 的 LINQ 请求?
How to correctly type a LINQ request declared as a class property?
C# 新手(实际上是 2 天前开始的),我有以下 class:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace MicrosoftReference.Linq
{
public class DataSets
{
public static IEnumerable Deck => from s in Suits() from r in Ranks() select new { Suit = s, Rank = r };
public static int DeckSize => Suits().Count() * Ranks().Count();
public static (IEnumerable, IEnumerable) Split()
{
return (Deck.Take(DeckSize / 2), startingDeck.Skip(DeckSize / 2));
}
static IEnumerable<string> Suits()
{
yield return "clubs";
yield return "diamonds";
yield return "hearts";
yield return "spades";
}
static IEnumerable<string> Ranks()
{
yield return "two";
yield return "three";
yield return "four";
yield return "five";
yield return "six";
yield return "seven";
yield return "eight";
yield return "nine";
yield return "ten";
yield return "jack";
yield return "queen";
yield return "king";
yield return "ace";
}
}
}
事实上,这段代码无法编译。在Split
方法中,调用Take
和Skip
方法是非法的,因为Deck在应该是IEnumerable<T>
的时候被声明为IEnumerable
。
从我从 IDE 中看到的,正确的类型应该是 IEnumerable<{string, string}>
但这种语法是非法的。然而,当我尝试另一种语法时,IDE 告诉我的是:
Cannot convert expression type
'System.Collections.Generic.IEnumerable<{string Suit, string Rank}>'
to return type 'System.Collections.Generic.IEnumerable
我该怎么办?
您可以使用 tuple:
public static IEnumerable<(string Suit, string Rank)> Deck => from s in Suits() from r in Ranks() select (Suit: s, Rank: r);
tymtam 直接回答了你的问题,但鉴于你是新人,我想提供一些建议。您似乎来自 python 的背景,这种背景更加自由。关于 C# 的最好的事情之一就是类型系统,而你做事的方式几乎失去了拥有它的所有好处。
其他情况这可能不适用,但如果您正在为一副标准纸牌建模并且只是一副标准纸牌,您就会知道花色和点数永远不会改变。这是 enums instead of strings. There are ways to enumerate over all the enum values 做您目前正在做的类似事情的完美情况。
其次,您有2个概念可以完美地封装在类中。你有一个 Card
并且你有一个 Deck
。如果您使用枚举,您可以像这样对 Card
建模:
public class Card
{
public Suit Suit { get; init; }
public Rank Rank { get; init; }
}
或者您甚至可以使用新的 record 类型:
public record Card(Suit Suit, Rank Rank);
然后对 Deck
做同样的事情
public class Deck
{
public IEnumerable<Card> Cards { get; }
}
如果您以这种方式建模,您可以将 Deck
类型设置为具有 Split
方法,而不是使用 static
方法。
public class Deck
{
private int DeckSize => this.Cards.Count();
public IEnumerable<Card> Cards { get; }
public (IEnumerable<Card>, IEnumerable<Card>) Split()
{
return (this.Cards.Take(this.DeckSize / 2), this.Cards.Skip(this.DeckSize / 2));
}
}
更好的是,您可以 return 2 个新的 Deck
而不是 IEnumerable<Card>
。这样你就可以再次拆分 returned Deck
中的每一个。
另外,我建议不要使用 linq
的风格,而是选择学习 method/fluent syntax。至少根据我个人的经验,没有多少开发人员熟悉 Query
语法,而是会使用 Method
语法。它也是普通的C#而不是特殊的语法,所以应该更容易理解。
最好的解决方案是说服您的项目负责人更改 属性 Deck
,使其 return 成为正确的 Deck 序列
class PlayCard // TODO: invent proper name
{
public string Suit {get; set;}
public string Rank {get; set;}
}
public static IEnumerable<PlayCard> Deck => from s in ... select new PlayCard
{
Suit = ...
Rank = ...
})
或者当使用方法语法时:
public IEnumerable<PlayCard> Deck => this.Suits.SelectMany(suit => this.Ranks,
(suit, rank) => new PlayCard
{
Suit = suit,
Rank = rand,
});
如果你真的不允许更改属性 Deck 的return 值,那么官方你无法确定这个return 值的序列包含什么。唯一可以保证的是它是一个对象序列。
但是,如果您仍然确定 属性 Deck 包含一系列 PlayCards,请考虑创建一个将对象转换为 PlayCards 的过程
public PlayCard ToPlayCard(object obj)
{
...
}
为此,您需要阅读有关反射的内容:Object.GetType()
和 Type.GetProperty
。
这不是很有效。
IEnumerable<PlayCard> playCards = Deck.Select(obj => ToPlayCard(obj);
请注意,在 属性 Deck 的 return 值发生最细微的变化后,此过程将不再有效。 return 向外界传递值时始终使用通用 类 的另一个原因
C# 新手(实际上是 2 天前开始的),我有以下 class:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace MicrosoftReference.Linq
{
public class DataSets
{
public static IEnumerable Deck => from s in Suits() from r in Ranks() select new { Suit = s, Rank = r };
public static int DeckSize => Suits().Count() * Ranks().Count();
public static (IEnumerable, IEnumerable) Split()
{
return (Deck.Take(DeckSize / 2), startingDeck.Skip(DeckSize / 2));
}
static IEnumerable<string> Suits()
{
yield return "clubs";
yield return "diamonds";
yield return "hearts";
yield return "spades";
}
static IEnumerable<string> Ranks()
{
yield return "two";
yield return "three";
yield return "four";
yield return "five";
yield return "six";
yield return "seven";
yield return "eight";
yield return "nine";
yield return "ten";
yield return "jack";
yield return "queen";
yield return "king";
yield return "ace";
}
}
}
事实上,这段代码无法编译。在Split
方法中,调用Take
和Skip
方法是非法的,因为Deck在应该是IEnumerable<T>
的时候被声明为IEnumerable
。
从我从 IDE 中看到的,正确的类型应该是 IEnumerable<{string, string}>
但这种语法是非法的。然而,当我尝试另一种语法时,IDE 告诉我的是:
Cannot convert expression type 'System.Collections.Generic.IEnumerable<{string Suit, string Rank}>' to return type 'System.Collections.Generic.IEnumerable
我该怎么办?
您可以使用 tuple:
public static IEnumerable<(string Suit, string Rank)> Deck => from s in Suits() from r in Ranks() select (Suit: s, Rank: r);
tymtam 直接回答了你的问题,但鉴于你是新人,我想提供一些建议。您似乎来自 python 的背景,这种背景更加自由。关于 C# 的最好的事情之一就是类型系统,而你做事的方式几乎失去了拥有它的所有好处。
其他情况这可能不适用,但如果您正在为一副标准纸牌建模并且只是一副标准纸牌,您就会知道花色和点数永远不会改变。这是 enums instead of strings. There are ways to enumerate over all the enum values 做您目前正在做的类似事情的完美情况。
其次,您有2个概念可以完美地封装在类中。你有一个 Card
并且你有一个 Deck
。如果您使用枚举,您可以像这样对 Card
建模:
public class Card
{
public Suit Suit { get; init; }
public Rank Rank { get; init; }
}
或者您甚至可以使用新的 record 类型:
public record Card(Suit Suit, Rank Rank);
然后对 Deck
public class Deck
{
public IEnumerable<Card> Cards { get; }
}
如果您以这种方式建模,您可以将 Deck
类型设置为具有 Split
方法,而不是使用 static
方法。
public class Deck
{
private int DeckSize => this.Cards.Count();
public IEnumerable<Card> Cards { get; }
public (IEnumerable<Card>, IEnumerable<Card>) Split()
{
return (this.Cards.Take(this.DeckSize / 2), this.Cards.Skip(this.DeckSize / 2));
}
}
更好的是,您可以 return 2 个新的 Deck
而不是 IEnumerable<Card>
。这样你就可以再次拆分 returned Deck
中的每一个。
另外,我建议不要使用 linq
的风格,而是选择学习 method/fluent syntax。至少根据我个人的经验,没有多少开发人员熟悉 Query
语法,而是会使用 Method
语法。它也是普通的C#而不是特殊的语法,所以应该更容易理解。
最好的解决方案是说服您的项目负责人更改 属性 Deck
,使其 return 成为正确的 Deck 序列
class PlayCard // TODO: invent proper name
{
public string Suit {get; set;}
public string Rank {get; set;}
}
public static IEnumerable<PlayCard> Deck => from s in ... select new PlayCard
{
Suit = ...
Rank = ...
})
或者当使用方法语法时:
public IEnumerable<PlayCard> Deck => this.Suits.SelectMany(suit => this.Ranks,
(suit, rank) => new PlayCard
{
Suit = suit,
Rank = rand,
});
如果你真的不允许更改属性 Deck 的return 值,那么官方你无法确定这个return 值的序列包含什么。唯一可以保证的是它是一个对象序列。
但是,如果您仍然确定 属性 Deck 包含一系列 PlayCards,请考虑创建一个将对象转换为 PlayCards 的过程
public PlayCard ToPlayCard(object obj)
{
...
}
为此,您需要阅读有关反射的内容:Object.GetType()
和 Type.GetProperty
。
这不是很有效。
IEnumerable<PlayCard> playCards = Deck.Select(obj => ToPlayCard(obj);
请注意,在 属性 Deck 的 return 值发生最细微的变化后,此过程将不再有效。 return 向外界传递值时始终使用通用 类 的另一个原因