问题描述
C# 新手(实际上是 2 天前开始的),我有以下课程:
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
时被声明为 IEnumerable<T>
。
从我从 IDE 中看到的,正确的类型应该是 IEnumerable<{string,string}>
但这种语法是非法的。然而,当我尝试另一种语法时,IDE 告诉我的是:
无法转换表达式类型 'System.Collections.Generic.IEnumerable' 返回类型'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 而不是字符串的完美情况。有多种方法可以枚举所有 enum values 以执行您当前正在执行的类似操作。
其次,您有两个可以完美封装在类中的概念。你有一个 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));
}
}
更好的是,您可以返回 2 个新的 Deck
而不是 IEnumerable<Card>
。这样您就可以再次拆分每个返回的 Deck
。
另外一点,我建议不要使用这种风格的 linq
,而是选择学习 method/fluent syntax。至少根据我的个人经验,没有多少开发人员熟悉 Query
语法并会改用 Method
语法。也是普通的C#而不是特殊的语法,所以应该更容易理解。
最好的解决方案是说服您的项目负责人更改属性 Deck
,使其返回正确的 Decks 序列
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的返回值,那么官方你无法确定这个返回值的序列包含什么。唯一可以保证的是它是一个对象序列。
但是,如果您仍然确定属性 Deck 包含一系列 PlayCard,请考虑创建一个将对象转换为 PlayCard 的过程
public PlayCard ToPlayCard(object obj)
{
...
}
为此,您需要阅读有关反射的内容:Object.GetType()
和 Type.GetProperty
。
这不是很有效。
IEnumerable<PlayCard> playCards = Deck.Select(obj => ToPlayCard(obj);
但是请注意,在属性 Deck 的返回值稍有变化后,此过程将不再起作用。向外界返回值时总是使用泛型类的另一个原因