如何正确键入声明为类属性的 LINQ 请求?

问题描述

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 方法中,调用 TakeSkip 方法是非法的,因为 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 的返回值稍有变化后,此过程将不再起作用。向外界返回值时总是使用泛型类的另一个原因