为什么要使用IList或List?

cho*_*bo2 76 c#

我知道这里有很多帖子,但它仍然让我困惑为什么你应该传递像IList这样的界面并返回像IList这样的界面而不是具体的列表.

我阅读了很多帖子,说明如何更容易在以后更改实现,但我只是不完全看到它是如何工作的.

假设我有这种方法

  public class SomeClass
    {
        public bool IsChecked { get; set; }
    }

 public void LogAllChecked(IList<SomeClass> someClasses)
    {
        foreach (var s in someClasses)
        {
            if (s.IsChecked)
            {
                // log 
            }

        }
    }
Run Code Online (Sandbox Code Playgroud)

我不确定将来如何使用IList将帮助我.

如果我已经在方法中怎么样?我还应该使用IList吗?

public void LogAllChecked(IList<SomeClass> someClasses)
    {
        //why not List<string> myStrings = new List<string>()
        IList<string> myStrings = new List<string>();

        foreach (var s in someClasses)
        {
            if (s.IsChecked)
            {
                myStrings.Add(s.IsChecked.ToString());
            }

        }
    }
Run Code Online (Sandbox Code Playgroud)

现在使用IList可以获得什么?

public IList<int> onlySomeInts(IList<int> myInts)
    {
        IList<int> store = new List<int>();
        foreach (var i in myInts)
        {
            if (i % 2 == 0)
            {
                store.Add(i);
            }
        }

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

现在怎么样?是否有一些int的列表的新实现我需要更改?

所以基本上我需要看一些实际的代码示例,说明如果我在哪里使用IList会解决一些问题而不是将List带入所有内容.

从我的阅读中我认为我可以使用IEnumberable而不是IList,因为我只是在循环中.

编辑 所以我一直在玩一些关于如何做到这一点的方法.我仍然不确定返回类型(如果我应该使它更具体或接口)

 public class CardFrmVm
    {
        public IList<TravelFeaturesVm> TravelFeaturesVm { get; set; }
        public IList<WarrantyFeaturesVm> WarrantyFeaturesVm { get; set; }

        public CardFrmVm()
        {
            WarrantyFeaturesVm = new List<WarrantyFeaturesVm>();
            TravelFeaturesVm = new List<TravelFeaturesVm>();
        }
}

 public class WarrantyFeaturesVm : AvailableFeatureVm
    {
    }

     public class TravelFeaturesVm : AvailableFeatureVm
    {

    }

      public class AvailableFeatureVm
    {
        public Guid FeatureId { get; set; }
        public bool HasFeature { get; set; }
        public string Name { get; set; }
    }


        private IList<AvailableFeature> FillAvailableFeatures(IEnumerable<AvailableFeatureVm> avaliableFeaturesVm)
        {
            List<AvailableFeature> availableFeatures = new List<AvailableFeature>();
            foreach (var f in avaliableFeaturesVm)
            {
                if (f.HasFeature)
                {
                                                    // nhibernate call to Load<>()
                    AvailableFeature availableFeature = featureService.LoadAvaliableFeatureById(f.FeatureId);
                    availableFeatures.Add(availableFeature);
                }
            }

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

所以我现在正在返回IList,因为我会将这个添加到我的域模型中,具有这样的属性

public virtual IList<AvailableFeature> AvailableFeatures { get; set; }
Run Code Online (Sandbox Code Playgroud)

以上是IList本身,因为这似乎是与nhibernate一起使用的标准.否则我可能已经返回IEnumberable但不确定.仍然无法弄清楚用户100%需要什么(这是返回具体优势的地方).

编辑2

我也在想如果我想在我的方法中通过引用传递会发生什么?

private void FillAvailableFeatures(IEnumerable<AvailableFeatureVm> avaliableFeaturesVm, IList<AvailableFeature> toFill)
            {

                foreach (var f in avaliableFeaturesVm)
                {
                    if (f.HasFeature)
                    {
                                                        // nhibernate call to Load<>()
                        AvailableFeature availableFeature = featureService.LoadAvaliableFeatureById(f.FeatureId);
                        toFill.Add(availableFeature);
                    }
                }
            }
Run Code Online (Sandbox Code Playgroud)

我会遇到这个问题吗?既然他们不能传入一个数组(具有固定大小)?对于具体的列表,它会更好吗?

Eri*_*ert 145

这里有三个问题:我应该使用什么类型的形式参数?我应该将什么用于局部变量?什么应该用于返回类型?

正式参数:

这里的原则是不要求超过你需要的.IEnumerable<T>传达"我需要从头到尾获得这个序列的元素".IList<T>传达"我需要以任意顺序获取和设置此序列的元素".List<T>沟通"我需要以任意顺序获取和设置此序列的元素,我只接受列表;我不接受数组."

通过要求超过您的需求,您(1)让呼叫者做不必要的工作以满足您的不必要的要求,以及(2)向读者传达虚假信息.只询问你将要使用的东西.这样,如果调用者有一个序列,他们就不需要在其上调用ToList来满足您的需求.

局部变量:

用你想要的任何东西.这是你的方法.您是唯一能够查看该方法的内部实现细节的人.

返回类型:

原理与之前相同,相反.提供您的呼叫者所需的最低限度.如果调用者只需要枚举序列的能力,只需给它们一个IEnumerable<T>.

  • 你怎么知道调用者需要什么.例如,我将我的一个返回类型切换为IList <>然后我很好,我可能只是枚举它们反正让我们只返回一个IEnumberable.然后我查看了我的视图(mvc)并发现我实际上需要count方法,因为我需要使用for循环.所以在我自己的应用程序中,我估计我实际需要的是什么,你如何预测别人需要或不需要什么. (9认同)
  • @ chobo2:那么,你如何预测调用者需要的*方法*?这似乎是首先要解决的问题.想必不知何故,你有办法知道为那些打电话给他们的人写些什么方法.问那些人他们想要什么方式返回.你的问题基本上是"我怎么知道要写什么软件?" 了解客户必须解决的问题,编写能够解决问题的代码. (5认同)
  • @kvb:差不多.考虑你的第一个场景.您可以通过将`items作为IList <T>`进行算法改进,如果得到非null,则使用改进的代码.这样你就可以利用,同时仍然允许客户灵活传递. (3认同)
  • 我不同意_返回类型_。我相信要让_消费者_高兴,你必须“少问多给”,因为你可以控制你得到的东西(_参数_但你无法控制消费者的期望,所以给予更多总是一个更好的主意恕我直言) 。例如使用“IEnumerable”(要求较少)作为参数,但返回“list”类型(提供更多)。 (3认同)

小智 29

我见过的最实际的原因是Jeffrey Richter在CLR中通过C#给出的.

模式是为参数提供可能最基类或接口,并为返回类型返回最具体的类或接口.这为您的调用者提供了将类型传递给方法的最大灵活性,以及​​投射/重用返回值的最大机会.

例如,以下方法

public void PrintTypes(IEnumerable items) 
{ 
    foreach(var item in items) 
        Console.WriteLine(item.GetType().FullName); 
}
Run Code Online (Sandbox Code Playgroud)

允许调用方法传递任何可以强制转换为可枚举的类型.如果你更具体

public void PrintTypes(List items)
Run Code Online (Sandbox Code Playgroud)

然后,比方说,如果你有一个数组并希望将它们的类型名称打印到控制台,你首先必须创建一个新的List并用你的类型填充它.而且,如果您使用了通用实现,那么您将只能使用仅适用于特定类型对象的任何对象的方法.

在谈论返回类型时,您的具体程度越高,调用者就越灵活.

public List<string> GetNames()
Run Code Online (Sandbox Code Playgroud)

您可以使用此返回类型来迭代名称

foreach(var name in GetNames())
Run Code Online (Sandbox Code Playgroud)

或者您可以直接索引到集合中

Console.WriteLine(GetNames()[0])
Run Code Online (Sandbox Code Playgroud)

然而,如果你回到一个不太具体的类型

public IEnumerable GetNames()
Run Code Online (Sandbox Code Playgroud)

你必须按摩返回类型才能获得第一个值

Console.WriteLine(GetNames().OfType<string>().First());
Run Code Online (Sandbox Code Playgroud)

  • 请注意,此建议与Eric Lippert在返回类型建议中的答案相矛盾.Jeffrey Richter的方法为方法的消费者提供了使用返回对象的最大灵活性,但他们喜欢,而Eric为方法的维护者提供了最大的灵活性来更改实现,而无需修改方法的公共表面.我倾向于遵循Jeffrey关于内部代码的建议,但对于公共图书馆,我可能更倾向于关注Eric的. (10认同)
  • @phoog:考虑到Eric的来源,他对破坏变化更加谨慎也就不足为奇了.但这绝对是一个有效的观点. (3认同)

Cha*_*ert 11

IEnumerable<T>允许您遍历集合.ICollection<T>以此为基础,并允许添加和删除项目.IList<T>还允许在特定索引处访问和修改它们.通过公开您期望消费者使用的那个,您可以自由地更改您的实现.List<T>碰巧实现了所有这三个接口.

如果您将属性公开为List<T>或者甚至是IList<T>您希望消费者拥有的属性,那么就可以遍历该集合.然后他们可以依赖于他们可以修改列表的事实.然后,如果您决定将实际数据存储从a转换List<T>为a Dictionary<T,U>并将字典键公开为属性的实际值(我之前必须完成此操作).那些期望他们的变化将反映在你的课堂内的消费者将不再拥有这种能力.那是个大问题!如果你暴露List<T>作为IEnumerable<T>你可以轻松预测到您的收藏没有被外部修改.这是List<T>作为任何上述接口暴露的权力之一.

当它属于方法参数时,这个抽象级别会转向另一个方向.将列表传递给接受的方法时,IEnumerable<T>可以确保不会修改列表.如果您是实现该方法的人并且您说您接受了一个,IEnumerable<T>因为您需要做的就是遍历该列表.然后调用该方法的人可以使用任何可枚举的数据类型来调用它.这允许您的代码以意想不到但完全有效的方式使用.

由此可见,您的方法实现可以表示您想要的局部变量.实现细节未公开.让您自由地将代码更改为更好的代码,而不会影响调用代码的人员.

你无法预测未来.假设属性的类型总是有益的,因为它List<T>会立即限制您适应代码无法预料的期望的能力.是的,您可能永远不会从a更改该数据类型,List<T>但如果必须,您可以确定.您的代码已准备就绪.


Adr*_*ian 9

简答:

您传递了接口,因此无论您使用的接口具体实现什么,您的代码都会支持它.

如果使用list的具体实现,则代码将不支持同一列表的另一个实现.

阅读一下继承和多态.


pho*_*oog 6

这是一个例子:我有一个项目,我们的列表非常大,导致大对象堆的碎片损害了性能.我们用LinkedList替换了List.LinkedList不包含数组,所以突然间,我们几乎没有使用大对象堆.

大多数情况下,IEnumerable<T>无论如何,我们都使用这些列表,因此不需要进一步更改.(是的,我建议将引用声明为IEnumerable,如果你所做的只是枚举它们.)在几个地方,我们需要列表索引器,所以我们IList<T>在链表上编写了一个低效的包装器.我们不经常需要列表索引器,因此低效率不是问题.如果是这样的话,我们本可以提供IList的一些其他实现,可能作为一个足够小的数组的集合,可以更有效地索引,同时避免大对象.

最后,您可能需要以任何理由替换实现; 表现只是一种可能性.无论什么原因,当您更改对象的特定运行时类型时,尽可能使用最少派生类型将减少代码更改的需要.