【问题标题】:Passing a single item as IEnumerable<T>将单个项目作为 IEnumerable<T> 传递
【发布时间】:2010-12-07 08:41:40
【问题描述】:

有没有一种通用的方法可以将T 类型的单个项目传递给需要IEnumerable&lt;T&gt; 参数的方法?语言为 C#,框架版本 2.0。

目前我正在使用辅助方法(它是 .Net 2.0,所以我有一大堆类似于 LINQ 的投射/投影辅助方法),但这似乎很愚蠢:

public static class IEnumerableExt
{
    // usage: IEnumerableExt.FromSingleItem(someObject);
    public static IEnumerable<T> FromSingleItem<T>(T item)
    {
        yield return item; 
    }
}

其他方式当然是创建和填充List&lt;T&gt;Array 并传递它而不是IEnumerable&lt;T&gt;

[编辑] 作为扩展方法,它可能被命名为:

public static class IEnumerableExt
{
    // usage: someObject.SingleItemAsEnumerable();
    public static IEnumerable<T> SingleItemAsEnumerable<T>(this T item)
    {
        yield return item; 
    }
}

我错过了什么吗?

[Edit2] 我们发现someObject.Yield()(正如@Peter 在下面的 cmets 中建议的那样)是此扩展方法的最佳名称,主要是为了简洁,所以这里与 XML 一起使用如果有人想抓住它,请发表评论:

public static class IEnumerableExt
{
    /// <summary>
    /// Wraps this object instance into an IEnumerable&lt;T&gt;
    /// consisting of a single item.
    /// </summary>
    /// <typeparam name="T"> Type of the object. </typeparam>
    /// <param name="item"> The instance that will be wrapped. </param>
    /// <returns> An IEnumerable&lt;T&gt; consisting of a single item. </returns>
    public static IEnumerable<T> Yield<T>(this T item)
    {
        yield return item;
    }
}

【问题讨论】:

  • 我将在扩展方法的主体中稍作修改:if (item == null) yield break; 现在您将停止传递 null 以及利用 IEnumerable 的(微不足道的)空对象模式. (foreach (var x in xs) 处理空的xs 就好了)。顺便说一句,这个函数是 IEnumerable&lt;T&gt; 列表 monad 的 monadic 单元,考虑到 Microsoft 的 monad love-fest,我很惊讶这样的东西一开始就没有在框架中。
  • 对于扩展方法,你不应该将它命名为AsEnumerable,因为一个名为already exists的内置扩展。 (当T 实现IEnumerable,例如string。)
  • 把方法命名为Yield怎么样?没有什么比简洁更重要的了。
  • 此处的命名建议。 “SingleItemAsEnumerable”有点冗长。 “产量”描述的是实现而不是接口——这不好。为了更好的名字,我建议使用“AsSingleton”,它与行为的确切含义相对应。
  • 我讨厌left==null 在这里检查。它破坏了代码的美感并阻止了代码的灵活性——如果有一天你需要生成一个可以为空的单例怎么办?我的意思是,new T[] { null }new T[] {} 不一样,总有一天你可能需要区分它们。

标签: c# .net generics ienumerable


【解决方案1】:

好吧,如果该方法需要 IEnumerable,您必须传递一个列表,即使它只包含一个元素。

通过

new[] { item }

我认为这个论点应该足够了

【讨论】:

    【解决方案2】:

    在 C# 3.0 中,您可以使用 System.Linq.Enumerable 类:

    // using System.Linq
    
    Enumerable.Repeat(item, 1);
    

    这将创建一个仅包含您的项目的新 IEnumerable。

    【讨论】:

    • 我认为这种解决方案会使代码更难阅读。 :( 在单个元素上重复是非常违反直觉的,你不觉得吗?
    • 重复一次对我来说没问题。更简单的解决方案,无需担心添加另一个扩展方法,并确保在您想使用它的任何地方都包含命名空间。
    • 是的,我实际上只是自己使用new[]{ item },我只是觉得这是一个有趣的解决方案。
    • 这个解决方案就是钱。
    • 恕我直言,这应该是公认的答案。它易于阅读、简洁,并且在 BCL 上的一行中实现,无需自定义扩展方法。
    【解决方案3】:

    IMO,您的辅助方法是最干净的方法。如果您传入一个列表或数组,那么一段不道德的代码可能会对其进行强制转换并更改其内容,从而在某些情况下导致奇怪的行为。您可以使用只读集合,但这可能涉及更多的包装。我认为您的解决方案非常简洁。

    【讨论】:

    • 如果列表/数组是临时构建的,它的范围在方法调用之后结束,所以它不应该引起问题
    • 如果以数组形式发送,如何更改?我想它可以被转换为一个数组,然后将引用更改为其他东西,但这有什么好处呢? (虽然我可能遗漏了一些东西......)
    • 假设您决定创建一个可枚举以传递给两个不同的方法...然后第一个将其转换为数组并更改内容。然后,您将它作为参数传递给另一个方法,而不知道发生了变化。我并不是说这很可能,只是在不需要的时候拥有可变的东西不如自然不变性那么整洁。
    • 这是一个被接受的答案,并且最有可能被阅读,所以我将在这里添加我的关注。我尝试了这种方法,但这破坏了我之前对myDict.Keys.AsEnumerable() 的编译调用,其中myDict 的类型为Dictionary&lt;CheckBox, Tuple&lt;int, int&gt;&gt;。在我将扩展方法重命名为SingleItemAsEnumerable 后,一切都开始工作了。无法将 IEnumerable>.KeyCollection>' 转换为 'IEnumerable'。存在显式转换(您是否缺少演员表?)我可以忍受一段时间的黑客攻击,但其他强大的黑客能找到更好的方法吗?
    • @HamishGrubijan:听起来您只想从dictionary.Values 开始。基本上不清楚发生了什么,但我建议你开始一个新的问题。
    【解决方案4】:

    在 C# 3(我知道你说过 2)中,你可以编写一个通用扩展方法,这可能会使语法更容易接受:

    static class IEnumerableExtensions
    {
        public static IEnumerable<T> ToEnumerable<T>(this T item)
        {
            yield return item;
        }
    }
    

    客户端代码为item.ToEnumerable()

    【讨论】:

    • 谢谢,我知道(如果我使用 C# 3.0 可以在 .Net 2.0 中工作),我只是想知道是否有内置机制。
    • 这是一个非常好的选择。我只想建议重命名为ToEnumerable 以避免与System.Linq.Enumerable.AsEnumerable 混淆
    • 附带说明,IObservable&lt;T&gt; 已经存在一个 ToEnumerable 扩展方法,因此该方法名称也会干扰现有约定。老实说,我建议根本不要使用扩展方法,因为它通用。
    【解决方案5】:

    此辅助方法适用于 item 或 many。

    public static IEnumerable<T> ToEnumerable<T>(params T[] items)
    {
        return items;
    }    
    

    【讨论】:

    • 有用的变体,因为它支持多个项目。我喜欢它依赖params 来构建数组,因此生成的代码看起来很干净。还没有决定我是否比new T[]{ item1, item2, item3 }; 更喜欢多个项目。
    • 聪明!喜欢它
    • 鉴于它不需要this 参数,因此不会应用于对象,我认为AsEnumerableToEnumerable 更好。
    【解决方案6】:

    我有点惊讶,没有人建议使用 T 类型的参数对方法进行新的重载来简化客户端 API。

    public void DoSomething<T>(IEnumerable<T> list)
    {
        // Do Something
    }
    
    public void DoSomething<T>(T item)
    {
        DoSomething(new T[] { item });
    }
    

    现在您的客户端代码可以这样做了:

    MyItem item = new MyItem();
    Obj.DoSomething(item);
    

    或使用列表:

    List<MyItem> itemList = new List<MyItem>();
    Obj.DoSomething(itemList);
    

    【讨论】:

    • 更好的是,您可以使用DoSomething&lt;T&gt;(params T[] items),这意味着编译器将处理从单个项目到数组的转换。 (这也将允许您传入多个单独的项目,并且编译器会再次为您处理将它们转换为数组。)
    • 我想我也更喜欢这个,它可以在没有泛型参数 T 的情况下使用 new[] { item },并且如果多次使用,可以使客户端使用语法更清晰。
    【解决方案7】:

    要么(如前所述)

    MyMethodThatExpectsAnIEnumerable(new[] { myObject });
    

    MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(myObject, 1));
    

    顺便说一句,如果你想要一个匿名对象的空列表,最后一个版本也很不错,例如

    var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));
    

    【讨论】:

    • 谢谢,尽管 Enumerable.Repeat 在 .Net 3.5 中是新的。它的行为似乎类似于上面的辅助方法。
    【解决方案8】:

    正如我刚刚发现并看到用户 LukeH 也建议的那样,一个不错的简单方法如下:

    public static void PerformAction(params YourType[] items)
    {
        // Forward call to IEnumerable overload
        PerformAction(items.AsEnumerable());
    }
    
    public static void PerformAction(IEnumerable<YourType> items)
    {
        foreach (YourType item in items)
        {
            // Do stuff
        }
    }
    

    此模式将允许您以多种方式调用相同的功能:单个项目;多个项目(逗号分隔);数组;一个列表;枚举等。

    虽然我不能 100% 确定使用 AsEnumerable 方法的效率,但它确实是一种享受。

    更新:AsEnumerable 函数看起来非常高效! (reference)

    【讨论】:

    • 其实你根本不需要.AsEnumerable()。数组YourType[] 已经实现了IEnumerable&lt;YourType&gt;。但是我的问题是指只有第二种方法(在您的示例中)可用并且您使用的是 .NET 2.0,并且您想要传递单个项目的情况。
    • 是的,确实如此,但您会发现您可能会遇到堆栈溢出 ;-)
    • 要真正回答您真正的问题(不是我真正想为自己回答的问题!),是的,您几乎必须按照马里奥的回答将其转换为数组
    • 定义一个IEnumerable&lt;T&gt; MakeEnumerable&lt;T&gt;(params T[] items) {return items;} 方法怎么样?然后可以将它与任何期望 IEnumerable&lt;T&gt; 的东西一起使用,用于任意数量的离散项目。代码本质上应该与定义一个返回单个项目的特殊类一样有效。
    【解决方案9】:

    虽然对于一种方法来说有点矫枉过正,但我​​相信有些人可能会发现交互式扩展很有用。

    Microsoft 的 Interactive Extensions (Ix) 包括以下方法。

    public static IEnumerable<TResult> Return<TResult>(TResult value)
    {
        yield return value;
    }
    

    可以这样使用:

    var result = EnumerableEx.Return(0);
    

    Ix 添加了原始 Linq 扩展方法中没有的新功能,是创建反应式扩展 (Rx) 的直接结果。

    想想,Linq Extension Methods + Ix = Rx IEnumerable

    Rx and Ix on CodePlex 都可以找到。

    【讨论】:

      【解决方案10】:

      我同意 @EarthEngine 的 cmets 对原始帖子的看法,即“AsSingleton”是一个更好的名字。 See this wikipedia entry。然后根据单例的定义,如果将空值作为参数传递,则“AsSingleton”应该返回一个带有单个空值的 IEnumerable,而不是一个空的 IEnumerable,这将解决if (item == null) yield break; 的争论。我认为最好的解决方案是有两种方法:“AsSingleton”和“AsSingletonOrEmpty”;其中,如果将 null 作为参数传递,“AsSingleton”将返回单个 null 值,而“AsSingletonOrEmpty”将返回一个空的 IEnumerable。像这样:

      public static IEnumerable<T> AsSingletonOrEmpty<T>(this T source)
      {
          if (source == null)
          {
              yield break;
          }
          else
          {
              yield return source;
          }
      }
      
      public static IEnumerable<T> AsSingleton<T>(this T source)
      {
          yield return source;
      }
      

      然后,这些或多或少类似于 IEnumerable 上的 'First' 和 'FirstOrDefault' 扩展方法,感觉不错。

      【讨论】:

        【解决方案11】:

        由于this C# compiler optimization,在foreach 中使用时,这比yieldEnumerable.Repeat 快30%,在其他情况下性能相同。

        public struct SingleSequence<T> : IEnumerable<T> {
            public struct SingleEnumerator : IEnumerator<T> {
                private readonly SingleSequence<T> _parent;
                private bool _couldMove;
                public SingleEnumerator(ref SingleSequence<T> parent) {
                    _parent = parent;
                    _couldMove = true;
                }
                public T Current => _parent._value;
                object IEnumerator.Current => Current;
                public void Dispose() { }
        
                public bool MoveNext() {
                    if (!_couldMove) return false;
                    _couldMove = false;
                    return true;
                }
                public void Reset() {
                    _couldMove = true;
                }
            }
            private readonly T _value;
            public SingleSequence(T value) {
                _value = value;
            }
            public IEnumerator<T> GetEnumerator() {
                return new SingleEnumerator(ref this);
            }
            IEnumerator IEnumerable.GetEnumerator() {
                return new SingleEnumerator(ref this);
            }
        }
        

        在这个测试中:

            // Fastest among seqs, but still 30x times slower than direct sum
            // 49 mops vs 37 mops for yield, or c.30% faster
            [Test]
            public void SingleSequenceStructForEach() {
                var sw = new Stopwatch();
                sw.Start();
                long sum = 0;
                for (var i = 0; i < 100000000; i++) {
                    foreach (var single in new SingleSequence<int>(i)) {
                        sum += single;
                    }
                }
                sw.Stop();
                Console.WriteLine($"Elapsed {sw.ElapsedMilliseconds}");
                Console.WriteLine($"Mops {100000.0 / sw.ElapsedMilliseconds * 1.0}");
            }
        

        【讨论】:

        • 谢谢,为这种情况创建一个结构以减少紧密循环中的 GC 负担是有意义的。
        • 枚举数和枚举数在返回时都会被装箱......
        • 不要使用秒表进行比较。使用 Benchmark.NET github.com/dotnet/BenchmarkDotNet
        • 刚刚测量过,new [] { i } 选项比您的选项快大约 3.2 倍。此外,您的选择比使用yield return 扩展快约 1.2 倍。
        • 您可以通过使用另一种 C# 编译器优化来加快速度:添加一个方法 SingleEnumerator GetEnumerator() 来返回您的结构。 C# 编译器将使用此方法(通过鸭子类型查找)而不是接口方法,从而避免装箱。许多内置集合都利用了这个技巧,比如List。注意:这仅在您直接引用 SingleSequence 时才有效,就像在您的示例中一样。如果将其存储在IEnumerable&lt;&gt; 变量中,则该技巧将不起作用(将使用接口方法)
        【解决方案12】:

        IanG 具有 a good post on the topic,建议将 EnumerableFrom() 作为名称(并提到 Haskell 和 Rx 将其称为 Return)。 IIRC F# 也称它为 Return。 F#的Seq calls the operator singleton&lt;'T&gt;

        如果您准备好以 C# 为中心,那么您可以将其称为 Yield [暗指参与实现它的 yield return]。

        如果您对它的性能方面感兴趣,James Michael Hare 也有一个returning zero or one items post,非常值得一看。

        【讨论】:

          【解决方案13】:

          这可能不会更好,但它有点酷:

          Enumerable.Range(0, 1).Select(i => item);
          

          【讨论】:

          • 不是。不一样。
          • Ewww。这是很多细节,只是为了将一个项目变成一个可枚举的。
          • 这相当于使用Rube Goldberg machine 做早餐。是的,它有效,但它要跳过 10 圈才能到达那里。当在执行了数百万次的紧密循环中完成时,像这样的简单事情可能会成为性能瓶颈。在实践中,性能方面在 99% 的情况下并不重要,但我个人仍然认为这是不必要的矫枉过正。
          【解决方案14】:

          有时我会这样做,当我觉得自己很顽皮时:

          "_".Select(_ => 3.14)  // or whatever; any type is fine
          

          这与较少的 shift 按键相同,呵呵:

          from _ in "_" select 3.14
          

          对于一个实用函数,我发现它是最不冗长的,或者至少比数组更具自文档性,尽管它会让多个值滑动;另外,它可以定义为本地函数:

          static IEnumerable<T> Enumerate (params T[] v) => v;
          // usage:
          IEnumerable<double> example = Enumerate(1.234);
          

          以下是我能想到的所有其他方式 (runnable here):

          using System;
          using System.Collections.Generic;
          using System.Linq;
          
          public class Program {
              
              public static IEnumerable<T> ToEnumerable1 <T> (T v) {
                  yield return v;
              }
              
              public static T[] ToEnumerable2 <T> (params T[] vs) => vs;
              
              public static void Main () {
                  static IEnumerable<T> ToEnumerable3 <T> (params T[] v) => v;
                  p( new string[] { "three" } );
                  p( new List<string> { "three" } );
                  p( ToEnumerable1("three") ); // our utility function (yield return)
                  p( ToEnumerable2("three") ); // our utility function (params)
                  p( ToEnumerable3("three") ); // our local utility function (params)
                  p( Enumerable.Empty<string>().Append("three") );
                  p( Enumerable.Empty<string>().DefaultIfEmpty("three") );
                  p( Enumerable.Empty<string>().Prepend("three") );
                  p( Enumerable.Range(3, 1) ); // only for int
                  p( Enumerable.Range(0, 1).Select(_ => "three") );
                  p( Enumerable.Repeat("three", 1) );
                  p( "_".Select(_ => "three") ); // doesn't have to be "_"; just any one character
                  p( "_".Select(_ => 3.3333) );
                  p( from _ in "_" select 3.0f );
                  p( "a" ); // only for char
                  // these weren't available for me to test (might not even be valid):
                  //   new Microsoft.Extensions.Primitives.StringValues("three")
                  
              }
          
              static void p <T> (IEnumerable<T> e) =>
                  Console.WriteLine(string.Join(' ', e.Select((v, k) => $"[{k}]={v,-8}:{v.GetType()}").DefaultIfEmpty("<empty>")));
          
          }
          

          【讨论】:

          • Evil hack :) 但我认为您不需要在 _ 参数周围加上括号,只需 .Select(_ =&gt; 3.333)
          • @Groo 谢谢!我经常忘记这一点。经过测试和修复。
          【解决方案15】:

          我想说的最简单的方法是new T[]{item};;没有语法可以做到这一点。我能想到的最接近的等价物是 params 关键字,但当然这要求您可以访问方法定义并且只能用于数组。

          【讨论】:

            【解决方案16】:
            Enumerable.Range(1,1).Select(_ => {
                //Do some stuff... side effects...
                return item;
            });
            

            上面的代码在使用like时很有用

            var existingOrNewObject = MyData.Where(myCondition)
                   .Concat(Enumerable.Range(1,1).Select(_ => {
                       //Create my object...
                       return item;
                   })).Take(1).First();
            

            上面的代码sn-p中没有空/null检查,保证只返回一个对象,不怕异常。此外,由于它是惰性的,因此在证明没有现有数据符合条件之前,不会执行闭包。

            【讨论】:

            • 这个答案被自动标记为“低质量”。如help 中所述(“简洁是可以接受的,但更全面的解释更好。”),请编辑它以告诉 OP 他做错了什么,您的代码是关于什么的。
            • 我看到这个答案的大部分内容,包括我正在回复的部分,都是后来被其他人编辑的。但是,如果第二个 sn-p 的目的是在 MyData.Where(myCondition) 为空时提供默认值,那么使用 DefaultIfEmpty():var existingOrNewObject = MyData.Where(myCondition).DefaultIfEmpty(defaultValue).First(); 已经可以(并且更简单)。如果您想要 default(T) 而不是自定义值,则可以进一步简化为 var existingOrNewObject = MyData.FirstOrDefault(myCondition);
            【解决方案17】:

            我最近在另一个帖子 (Is there a way to call a C# method requiring an IEnumerable<T> with a single value? ...with benchmarking) 上问了同样的问题。

            我希望人们在这里停下来看看在较新的帖子中显示的简短基准比较,用于这些答案中提出的 4 种方法。

            似乎在方法的参数中简单地写new[] { x }是最短和最快的解决方案。

            【讨论】:

              【解决方案18】:

              要在“不一定是解决方案,但仍然...解决方案”或“愚蠢的 LINQ 技巧”下归档,您可以将 Enumerable.Empty&lt;&gt;()Enumerable.Append&lt;&gt;() 结合使用...

              IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Append("Hello, World!");
              

              ...或Enumerable.Prepend&lt;&gt;()...

              IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Prepend("Hello, World!");
              

              后两种方法从 .NET Framework 4.7.1 和 .NET Core 1.0 开始可用。

              如果一个人真的打算使用现有方法而不是编写自己的方法,这是一个可行的解决方案,尽管我不确定这是否比the Enumerable.Repeat&lt;&gt;() solution 更清楚或更少。这绝对是更长的代码(部分原因是无法对Empty&lt;&gt;() 进行类型参数推断)并且创建了两倍的枚举器对象。

              完善这个“你知道这些方法存在吗?”回答,Array.Empty&lt;&gt;() 可以代替 Enumerable.Empty&lt;&gt;(),但很难说这会使情况...更好。

              【讨论】:

                【解决方案19】:

                我参加聚会有点晚了,但无论如何我都会分享我的方式。 我的问题是我想将 ItemSource 或 WPF TreeView 绑定到单个对象。层次结构如下所示:

                项目 > 地块 > 房间

                总是只有一个项目,但我仍然想在树中显示项目,而不必像一些建议的那样传递一个只有一个对象的集合。
                由于您只能将 IEnumerable 对象作为 ItemSource 传递,我决定让我的类 IEnumerable:

                public class ProjectClass : IEnumerable<ProjectClass>
                {
                    private readonly SingleItemEnumerator<AufmassProjekt> enumerator;
                
                    ... 
                
                    public IEnumerator<ProjectClass > GetEnumerator() => this.enumerator;
                
                    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
                }
                

                并相应地创建我自己的枚举器:

                public class SingleItemEnumerator : IEnumerator
                {
                    private bool hasMovedOnce;
                
                    public SingleItemEnumerator(object current)
                    {
                        this.Current = current;
                    }
                
                    public bool MoveNext()
                    {
                        if (this.hasMovedOnce) return false;
                        this.hasMovedOnce = true;
                        return true;
                    }
                
                    public void Reset()
                    { }
                
                    public object Current { get; }
                }
                
                public class SingleItemEnumerator<T> : IEnumerator<T>
                {
                    private bool hasMovedOnce;
                
                    public SingleItemEnumerator(T current)
                    {
                        this.Current = current;
                    }
                
                    public void Dispose() => (this.Current as IDisposable).Dispose();
                
                    public bool MoveNext()
                    {
                        if (this.hasMovedOnce) return false;
                        this.hasMovedOnce = true;
                        return true;
                    }
                
                    public void Reset()
                    { }
                
                    public T Current { get; }
                
                    object IEnumerator.Current => this.Current;
                }
                

                这可能不是“最干净”的解决方案,但它对我有用。

                编辑
                正如@Groo 指出的那样,为了维护single responsibility principle,我创建了一个新的包装类:

                public class SingleItemWrapper : IEnumerable
                {
                    private readonly SingleItemEnumerator enumerator;
                
                    public SingleItemWrapper(object item)
                    {
                        this.enumerator = new SingleItemEnumerator(item);
                    }
                
                    public object Item => this.enumerator.Current;
                
                    public IEnumerator GetEnumerator() => this.enumerator;
                }
                
                public class SingleItemWrapper<T> : IEnumerable<T>
                {
                    private readonly SingleItemEnumerator<T> enumerator;
                
                    public SingleItemWrapper(T item)
                    {
                        this.enumerator = new SingleItemEnumerator<T>(item);
                    }
                
                    public T Item => this.enumerator.Current;
                
                    public IEnumerator<T> GetEnumerator() => this.enumerator;
                
                    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
                }
                

                我是这样用的

                TreeView.ItemSource = new SingleItemWrapper(itemToWrap);
                

                编辑 2
                我用MoveNext() 方法纠正了一个错误。

                【讨论】:

                • SingleItemEnumerator&lt;T&gt; 类是有道理的,但是让一个类成为“自身的单个项目 IEnumerable”似乎违反了单一责任原则。也许它使传递它更实用,但我仍然更愿意根据需要包装它。
                【解决方案20】:

                我更喜欢

                public static IEnumerable<T> Collect<T>(this T item, params T[] otherItems)
                {
                    yield return item;
                    foreach (var otherItem in otherItems)
                    {
                        yield return otherItem;
                    }
                }
                

                如果你想要单例,这可以让你调用item.Collect(),但如果你愿意,它也可以让你调用item.Collect(item2, item3)

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2011-08-22
                  • 2015-03-19
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-08-22
                  相关资源
                  最近更新 更多