如何正确键入声明为 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方法中,调用TakeSkip方法是非法的,因为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 向外界传递值时始终使用通用 类 的另一个原因