我有一个关于性能和最佳实践的一般性问题。
当使用来自不同类的列表(或任何其他数据类型)时,哪种做法更好? 从一开始就复制,使用本地,然后将其重新复制到原始版本,还是始终访问原始版本?
示例:
访问原文:
public class A
{
public static List<int> list = new List<int>();
}
public class B
{
public static void insertString(int i)
{
// insert at right place
int count = A.list.Count;
if (count == 0)
{
A.list.Add(i);
}
else
{
for (int j = 0; j < count; j++)
{
if (A.list[j] >= i)
{
A.list.Insert(j, i);
break;
}
if (j == count - 1)
{
A.list.Add(i);
}
}
}
}
}
如您所见,我多次访问原始列表 A.list。这是替代方案:
复制:
public class A
{
public static List<int> list = new List<int>();
}
public class B
{
public static void insertString(int i)
{
List<int> localList = A.list;
// insert at right place
int count = localList.Count;
if (count == 0)
{
localList.Add(i);
}
else
{
for (int j = 0; j < count; j++)
{
if (localList[j] >= i)
{
localList.Insert(j, i);
break;
}
if (j == count - 1)
{
localList.Add(i);
}
}
}
A.list = localList;
}
}
在这里,我仅访问另一个类中的列表两次(在开头获取并在结尾设置)。哪个会更好。
请注意,这是一个一般性问题,该算法只是一个示例。
我不会在这里费心考虑性能,而是专注于最佳实践:
给出整个列表违反了封装性。 B可以在A不注意的情况下修改列表及其所有元素(如果A从不使用列表本身,那么A甚至不需要存储它,这不是问题)。
一个简单的例子:A创建List并立即添加一个元素。随后,A不再去检查
List.Count
,因为它知道List不能为空。现在 B 出现并清空列表...
因此,每当B发生更改时,您还需要检查A以查看A的所有假设是否仍然正确。如果您可以完全控制代码,这已经够让人头疼的了。如果另一个程序员使用你的类A,他可能会对列表做一些意想不到的事情,并且永远不会检查这是否可以。
解决方案:
如果B只需要迭代元素,则编写一个
IEnumerable
访问器。如果 B 不得修改元素,请让访问者传递副本。
如果B需要修改列表(添加/删除元素),请给B列表的副本(如果不需要修改,则包含元素的副本)并接受来自B的新列表 或像以前一样使用访问器并实现必要的列表操作。在这两种情况下,A都会知道B是否修改了列表并可以做出相应的反应。
示例:
class A
{
private List<ItemType> internalList;
public IEnumerable<ItemType> Items()
{
foreach (var item in internalList)
yield return item;
// or maybe item.Copy();
// new ItemType(item);
// depending on ItemType
}
public void RemoveFromList(ItemType toRemove)
{
internalList.Remove(toRemove);
// do other things necessary to keep A in a consistent state
}
}