代码之家  ›  专栏  ›  技术社区  ›  Joe

接口的getEnumerator的显式实现导致堆栈溢出

c#
  •  5
  • Joe  · 技术社区  · 15 年前

    我正在尽我最大的努力在可能的情况下对接口进行编码,但是当涉及到集合时,我遇到了一些问题。例如,这里有几个我想使用的接口。

    public interface IThing {}
    
    public interface IThings : IEnumerable<IThing> {}
    

    下面是实现。为了实现IEnumerable<Ithing>我需要显式实现IEnumerable<Ithing>.GetEnumerator()。

    public class Thing : IThing {}
    
    public class Things : List<Thing>, IThings
    {
        IEnumerator<IThing> IEnumerable<IThing>.GetEnumerator()
        {
            // This calls itself over and over
            return this.Cast<IThing>().GetEnumerator();
        }
    }
    

    问题是getEnumerator实现导致堆栈溢出。它一遍又一遍地召唤自己。我不明白为什么它决定调用GetEnumerator的实现,而不是此结果提供的实现。cast<ithing>()。知道我做错了什么吗?我敢打赌这是非常愚蠢的事情…

    下面是上述类的一些简单测试代码:

    static void Enumerate(IThings things)
    {
        foreach (IThing thing in things)
        {
            Console.WriteLine("You'll never get here.");
        }
    }
    
    static void Main()
    {
        Things things = new Things();
        things.Add(new Thing());
    
        Enumerate(things);
    }
    
    6 回复  |  直到 15 年前
        1
  •  3
  •   Stormenet    15 年前

    改为使用:

        public class Things : List<Thing>, IThings
        {
            IEnumerator<IThing> IEnumerable<IThing>.GetEnumerator()
            {
                foreach (Thing t in this)
                {
                    yield return t;
                }
            }
        }
    

    或者你可以用遏制来代替。

        2
  •  1
  •   Russell Steen    15 年前
    IEnumerator<IThing> IEnumerable<IThing>.GetEnumerator()
    {
        // This calls itself over and over
        return this.Cast<IThing>().GetEnumerator();
    }
    

    这是一个没有中断条件的递归调用,您将得到一个堆栈溢出,因为它将很快填满堆栈。

    您要强制转换到一个接口,它不会像您认为的那样工作。最后,您只需要调用这个.getEnumerator();这是您已经使用的函数,因此是递归的。也许您的意思是base.getEnumerator();

        3
  •  1
  •   Kyle Chafin    15 年前

    通常,您可能希望将Things集合的实现与Thing对象的实现分离。Things类能够处理IThing的任何实现,而不仅仅是Thing类。

    明确地:

    public class Things : List<IThing>, IThings
    {
    }
    

    在这种情况下,您不必重写getEnumerator()的默认实现,基本实现已经为您正确地键入了。这将避免您当前遇到的溢出,并满足您提供的测试用例。

        4
  •  1
  •   Frank Krueger    15 年前

    这是一个很好的例子,说明语言和运行时需要理解协方差和逆方差。

    在C 4中,您只需使用

    IEnumerator<IThing> IEnumerable<IThing>.GetEnumerator()
    {
        return base.GetEnumerator();
    }
    
        5
  •  0
  •   Will    15 年前

    似乎很明显 this.Cast<IThing>() 返回IEnumerable。或者,至少没有实现 Cast .

        6
  •  0
  •   Rune FS    15 年前

    我不知道一个问题是否可以作为一个答案,但请允许我作为一个顾问;p为什么要实现getEnumerator()?

    如果你把它改成list,你就可以免费得到getEnumerator。

    也要注意,从列表中派生通常是一个坏主意。您应该考虑从IEnumerable<()IThings>派生(因为List也实现了该接口,所以您已经在做什么是atm多余的),或者如果您需要List接口,而不仅仅是IEnumerable实现IList并保留私有的List对象。这样,您就可以完全控制实现,并且只公开设计契约(通过实现的接口)。