C# 如何创建一个类,该类可以是 int、null 或我刚才描述的同一类的数组?

Gri*_*zly 0 c# oop class data-structures

这是一个设计问题,我无法想象任何优雅的解决方案,您将如何创建所描述的类,因此,作为利用的示例,在实例化时您可以调用打印函数,如果它是 int,则只打印数字,如果它为空,则不会执行任何操作,如果它是一个数组,则打印该数组的第 i 个(我猜是内部迭代器)元素。这里什么都没有,提前谢谢!

感谢大家提出的答案,更具体地说明要求。

举个例子:[[1,2,3], null, 4, 5, [6,null,7]]这里的所有元素都是MyIntOrArray包括主数组本身的类型,因此当调用 print 7 次时,您将得到诸如“1”“2”“3”“4”“5”之类的输出“6” “7”

无论如何,我对Print功能不那么感兴趣,但对如何创建此类需求的实现/结构/类更感兴趣。

public class MyIntOrArray
{
    public int? Value { get; set; }
    public MyIntOrArray[] Values { get; set; }
    private bool AmIArray { get; set; }
    private int InnerIndex { get; set; }

    public MyIntOrArray(int? value)
    {
        Value = value;
        AmIArray = false;
    }

    public MyIntOrArray(int size, bool isArray)
    {
        Values = new MyIntOrArray[size];
        AmIArray = true;
    }

    public bool Print()
    {
        if(AmIArray)
        {
            if (InnerIndex < Values.Length)
            {
                if (Values[InnerIndex].Print())
                    InnerIndex += 1;

                return InnerIndex >= Values.Length ? true : false;
            }                
        }
        else if(Value != null) 
        {
            Console.WriteLine(Value.ToString());                
        }

        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ann 9

这里的大多数答案似乎都忽略了一个奇怪的要求,即如果内容是一个数组,该Print方法应该打印一个值并推进一个内部指针。我将在下面处理这个要求。

然而,OP 并没有指定如果调用Print足够多次来耗尽数组会发生什么。我假设在这种情况下,该方法不应再打印任何内容,但另一种选择是回绕并从头开始。

我将给出一个专业且通用的答案。

专业化实施

考虑到要求,您可以利用数组,包括其他两种替代方案作为特殊情况:Null(或无)是空数组,value是单例(单元素)数组。

因此,专门的实现可能如下所示:

public sealed class IntOrArrayOrNothing
{
    private readonly int[] ints;
    private int idx;

    public IntOrArrayOrNothing()
    {
        ints = Array.Empty<int>();
    }

    public IntOrArrayOrNothing(int i)
    {
        ints = new[] { i };
        idx = -1; // Sentinel value
    }

    public IntOrArrayOrNothing(int[] ints)
    {
        this.ints = ints;
    }

    public void Print()
    {
        if (ints.Length == 0 || ints.Length <= idx)
            return;

        if (idx == -1) // Check sentinel value indicating a single int
        {
            Console.WriteLine(ints[0]);
            return;
        }

        Console.WriteLine(ints[idx++]);
    }
}
Run Code Online (Sandbox Code Playgroud)

对这种专门实现的一个公平的批评是它不容易改变或扩展。如果您想在到达末尾时将索引回零怎么办?如果想一次性打印数组怎么办?如果您想做打印以外的其他事情怎么办?

通用设计

作为一般规则,每当您遇到类型应该是互斥替代方案之一的要求时,您就需要sum 类型

有些语言本身就支持这些。例如,在F#中,您只需声明如下类型:

type IntOrArrayOrNothing = Nothing | Int of int | Array of int array
Run Code Online (Sandbox Code Playgroud)

然而,也许值的实际类型(这里是int)并不重要,在这种情况下,您可以将类型设为泛型(或者参数多态):

type ValueOrCollectionOrNothing<'a> = Nothing | Value of 'a | Collection of 'a seq
Run Code Online (Sandbox Code Playgroud)

不幸的是,像 C# 这样的语言不支持开箱即用的求和类型,但并非所有内容都会丢失。您可以通过(至少)两种不同的方式之一实现自己的求和类型:

这些替代方案实际上是同构的。Church 编码更简洁,但由于这个问题被标记为oop,所以我将显示 Visitor 实现。

在这种特殊情况下,确实没有必要花这么多时间,因为即使使用泛型,无值值的情况也可以使用集合来实现。尽管如此,我仍将继续演示链接文章中讨论的一般技术如何应用​​于具体案例。

我还将代码实现为不可变值,因为可变状态通常只会使代码推理变得更加困难。

因此,首先声明一个类:

public sealed class ValueOrCollectionOrNothing<T>
{
    private readonly IValueOrCollectionOrNothing imp;

    public ValueOrCollectionOrNothing()
    {
        imp = new Nothing();
    }

    public ValueOrCollectionOrNothing(T value)
    {
        imp = new Value(value);
    }

    public ValueOrCollectionOrNothing(IReadOnlyCollection<T> values)
    {
        imp = new Collection(values);
    }

    public TResult Accept<TResult>(
        IValueOrCollectionOrNothingVisitor<T, TResult> visitor)
    {
        return imp.Accept(visitor);
    }

    private interface IValueOrCollectionOrNothing
    {
        TResult Accept<TResult>(
            IValueOrCollectionOrNothingVisitor<T, TResult> visitor);
    }

    // Implementation classes (see below) go here...
}
Run Code Online (Sandbox Code Playgroud)

请注意,三个构造函数重载初始化了IValueOrCollectionOrNothing接口的三个不同实现(见下文)。

IValueOrCollectionOrNothingVisitor界面是您实际“枚举”互斥情况的地方:

public interface IValueOrCollectionOrNothingVisitor<T, TResult>
{
    TResult VisitNothing();
    TResult VisitValue(T value);
    TResult VisitCollection(IReadOnlyCollection<T> collection);
}
Run Code Online (Sandbox Code Playgroud)

Accept方法需要一个访问者,并且只委托给imp.Accept

该类Nothing是一个嵌套类:

private sealed class Nothing : IValueOrCollectionOrNothing
{
    public TResult Accept<TResult>(
        IValueOrCollectionOrNothingVisitor<T, TResult> visitor)
    {
        return visitor.VisitNothing();
    }
}
Run Code Online (Sandbox Code Playgroud)

这同样适用于Value

private sealed class Value : IValueOrCollectionOrNothing
{
    private readonly T value;

    public Value(T value)
    {
        this.value = value;
    }

    public TResult Accept<TResult>(
        IValueOrCollectionOrNothingVisitor<T, TResult> visitor)
    {
        return visitor.VisitValue(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

Collection

private sealed class Collection : IValueOrCollectionOrNothing
{
    private readonly IReadOnlyCollection<T> collection;

    public Collection(IReadOnlyCollection<T> collection)
    {
        this.collection = collection;
    }

    public TResult Accept<TResult>(
        IValueOrCollectionOrNothingVisitor<T, TResult> visitor)
    {
        return visitor.VisitCollection(collection);
    }
}
Run Code Online (Sandbox Code Playgroud)

访客会是什么样子?

如果你想打印整个集合,你可以这样做:

public sealed class PrintVisitor : IValueOrCollectionOrNothingVisitor<int, IEnumerable<string>>
{
    public IEnumerable<string> VisitNothing()
    {
        return Array.Empty<string>();
    }

    public IEnumerable<string> VisitValue(int value)
    {
        return new[] { value.ToString() };
    }

    public IEnumerable<string> VisitCollection(IReadOnlyCollection<int> collection)
    {
        return collection.Take(1).Select(i => i.ToString());
    }
}
Run Code Online (Sandbox Code Playgroud)

另一方面,如果您想推进集合索引,您可以这样做:

public sealed class PrintAndChangeStateVisitor
    : IValueOrCollectionOrNothingVisitor<int, (IReadOnlyCollection<string>, PrintAndChangeStateVisitor)>
{
    private readonly int idx;

    public PrintAndChangeStateVisitor() : this(0)
    {
    }

    private PrintAndChangeStateVisitor(int idx)
    {
        this.idx = idx;
    }

    public (IReadOnlyCollection<string>, PrintAndChangeStateVisitor) VisitNothing()
    {
        return (Array.Empty<string>(), this);
    }

    public (IReadOnlyCollection<string>, PrintAndChangeStateVisitor) VisitValue(
        int value)
    {
        return (new[] { value.ToString() }, this);
    }

    public (IReadOnlyCollection<string>, PrintAndChangeStateVisitor) VisitCollection(
        IReadOnlyCollection<int> collection)
    {
        if (collection.Count <= idx)
            return (Array.Empty<string>(), this);

        return (
            new[] { collection.ElementAt(idx).ToString() },
            new PrintAndChangeStateVisitor(idx + 1));
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,这并不要求您更改ValueOrCollectionOrNothing<T>类;只需更改该类即可。你只需写另一个访客即可。因此,这样的设计更具可扩展性。