如何通过契约定义 IEnumerable 行为?
考虑这 2 个返回 IEnumerable 的方法:
private IEnumerable<MyClass> GetYieldResult(int qtResult)
{
for (int i = 0; i < qtResult; i++)
{
count++;
yield return new MyClass() { Id = i+1 };
}
}
private IEnumerable<MyClass> GetNonYieldResult(int qtResult)
{
var result = new List<MyClass>();
for (int i = 0; i < qtResult; i++)
{
count++;
result.Add(new MyClass() { Id = i + 1 });
}
return result;
}
此代码在调用 IEnumerable 的某些方法时显示 2 种不同的行为:
[TestMethod]
public void Test1()
{
count = 0;
IEnumerable<MyClass> yieldResult = GetYieldResult(1);
var firstGet = yieldResult.First();
var secondGet = yieldResult.First();
Assert.AreEqual(1, firstGet.Id);
Assert.AreEqual(1, secondGet.Id);
Assert.AreEqual(2, count);//calling "First()" 2 times, yieldResult is created 2 times
Assert.AreNotSame(firstGet, secondGet);//and created different instances of each list item
}
[TestMethod]
public void Test2()
{
count = 0;
IEnumerable<MyClass> yieldResult = GetNonYieldResult(1);
var firstGet = yieldResult.First();
var secondGet = yieldResult.First();
Assert.AreEqual(1, firstGet.Id);
Assert.AreEqual(1, secondGet.Id);
Assert.AreEqual(1, count);//as expected, it creates only 1 result set
Assert.AreSame(firstGet, secondGet);//and calling "First()" several times will always return same instance of MyClass
}
当我的代码返回 IEnumerables 时,选择我想要的行为很简单,但是如何显式定义某些方法获取 IEnumerable 作为参数无论调用“First()”方法多少次,都会创建单个结果集。
当然,我不想强制创建不必要的所有itens,并且我想将参数定义为IEnumerable,以表示不会在集合中包含或删除任何项目。
编辑:需要明确的是,问题不在于yield 如何工作,也不在于为什么 IEnumerable 可以为每次调用返回不同的实例。问题是,当我多次调用“First()”或“Take(1)”等方法时,如何指定参数应该是“仅搜索”集合,该集合返回相同的 MyClass 实例。
有什么想法吗?
提前致谢!
Consider this 2 methods that returns IEnumerable:
private IEnumerable<MyClass> GetYieldResult(int qtResult)
{
for (int i = 0; i < qtResult; i++)
{
count++;
yield return new MyClass() { Id = i+1 };
}
}
private IEnumerable<MyClass> GetNonYieldResult(int qtResult)
{
var result = new List<MyClass>();
for (int i = 0; i < qtResult; i++)
{
count++;
result.Add(new MyClass() { Id = i + 1 });
}
return result;
}
This code shows 2 different behaviors when calling some method of IEnumerable:
[TestMethod]
public void Test1()
{
count = 0;
IEnumerable<MyClass> yieldResult = GetYieldResult(1);
var firstGet = yieldResult.First();
var secondGet = yieldResult.First();
Assert.AreEqual(1, firstGet.Id);
Assert.AreEqual(1, secondGet.Id);
Assert.AreEqual(2, count);//calling "First()" 2 times, yieldResult is created 2 times
Assert.AreNotSame(firstGet, secondGet);//and created different instances of each list item
}
[TestMethod]
public void Test2()
{
count = 0;
IEnumerable<MyClass> yieldResult = GetNonYieldResult(1);
var firstGet = yieldResult.First();
var secondGet = yieldResult.First();
Assert.AreEqual(1, firstGet.Id);
Assert.AreEqual(1, secondGet.Id);
Assert.AreEqual(1, count);//as expected, it creates only 1 result set
Assert.AreSame(firstGet, secondGet);//and calling "First()" several times will always return same instance of MyClass
}
It's simple to choose which behavior I want when my code returns IEnumerables, but how can I explicitly define that some method gets an IEnumerable as parameter that creates a single result set dispite of how many times it calls "First()" method.
Of course, I don't want to force all itens to be created unnecessarily and I want to define the parameter as IEnumerable to say that no item will be included or removed from the collection.
EDIT: Just to be clear, the question is not about how yield works or why IEnumerable can return different instances for each call. The question is how can I specify that a parameter should be a "search only" collection that returns same instances of MyClass when I call methods like "First()" or "Take(1)" several times.
Any ideas?
Thanks in advance!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
在这种情况下,您需要允许该方法按需创建它们,并且如果对象是按需创建的(并且没有某种形式的缓存),它们将是 < em>不同对象(至少在不同引用的意义上——非值对象相等的默认定义)。
如果您的对象本质上是唯一的(即它们没有定义某些基于值的相等),那么每次调用 new 都会创建一个不同的对象(无论构造函数参数如何)。
所以答案是
是“你不能”,除非创建一组对象并重复返回同一组,或通过将相等性定义为不同的东西。
附加(基于评论)。如果您确实希望能够重播(需要一个更好的术语)同一组对象而不构建整个集合,您可以缓存已经生成的对象并首先重播。类似的东西:
我希望也可以围绕迭代器构建这样的缓存包装器(
while
将成为底层迭代器上的foreach
,但您需要缓存它迭代器或Skip
到需要的位置(如果调用者迭代超出缓存List
)。注意任何缓存方法都很难确保线程安全。
In which case you need to allow the method to create them on demand, and if objects are created on demand (and without some form of cache) they will be different objects (at least in the sense of being different references—the default definition of equality for non-value objects).
If your objects are inherently unique (i.e. they don't define some value based equality) then each call to
new
will create a different object (whatever the constructor parameters).So the answer to
is "you can't" except by creating one set of objects and repeatedly returning the same set, or by defining equality to be something different.
Additional (based on comments). If you really want to be able to replay (for want of a better term) the same set of objects without building the whole collection you could cache want has already been generated and replay that first. Something like:
I expect it would be possible to build such a caching wrapper around an iterator as well (
while
would becomeforeach
over underlying iterator, but you would need to cache that iterator orSkip
to the require position if the caller iterated beyond the cahingList
).NB any caching approach would be hard to make thread safe.
一段时间以来,我一直在努力寻找解决该问题的优雅方法。我希望框架设计者向 IEnumerable 添加一点“IsImmutable”或类似的属性 getter,以便可以轻松添加一个 Evaluate(或类似)扩展方法,该方法不会对已经处于“完全评估状态”的 IEnumerable 执行任何操作“ 状态。
然而,由于它不存在,所以这是我能想到的最好的:
扩展方法知道这一点
新界面以及
子集的不变性
我消费的相关 BCL 类型
最频繁。
我的“原始”BCL 集合类型
API 以提高我的 Evaluate 方法的效率(至少在针对我自己的代码运行时)。
这是相当笨拙的,但这是迄今为止我能够找到的侵入性最小的方法,用于解决允许 IEnumerable 使用者仅在实际需要时创建本地副本的问题。我非常希望你的问题能够引出一些更有趣的解决方案......
I've been trying to find an elegant solution to the problem for a while now. I wish that the framework designers had added a little "IsImmutable" or similar property getter to IEnumerable so that one could easily add an Evaluate (or similar) extension method that doesn't do anything for an IEnumerable that is already in its "fully evaluated" state.
However, since that doesn't exist, here's the best I've been able to come up with:
extension method is aware of this
new interface as well as the
immutability of the subset of
relevant BCL types that I consume
most frequently.
"raw" BCL collection types from my
APIs in order to increase the efficiency of my Evaluate method (at least when running against my own code).
It's rather kludgy, but it's the least intrusive approach I've been able to find so far to address the problem of allowing an IEnumerable consumer to create a local copy only when this is actually necessary. I very much hope that your question lures some more interesting solutions out of the woodwork...
除非我误读了你,否则你的问题可能是由误解引起的。没有任何东西会返回 IEnumerable。第一种情况返回一个枚举器,它实现了 foreach,允许您一次获取 MyClass 的实例。它(函数返回值)的类型为 IEnumerable,表明它支持 foreach 行为(以及其他一些行为)。
第二个函数实际上返回一个 List,当然它也支持 IEnumerable(foreach 行为)。但它是 MyClass 对象的实际具体集合,由您调用的方法(第二个)创建。
第一个方法根本不返回任何 MyClass 对象,它返回由 dotNet 框架创建并编码的枚举器对象每次迭代时都会在幕后实例化一个新的 MyClass 对象。
编辑:更多细节
一个更重要的区别是,您是否希望在迭代时在类中为您有状态地保留这些项目,或者是否希望在迭代时为您创建它们。
另一个考虑因素是..您希望退回给您的物品是否已经存在于其他地方?即,此方法是否要迭代某些现有集合的集合(或过滤后的子集)?或者是动态创建项目?如果是后者,那么每次您“获取”该项目时是否都是完全相同的实例,这重要吗?
对于定义为表示可称为实体的事物(具有定义的身份的事物)的对象,您可能希望连续的提取返回相同的实例。
但也许具有相同状态的另一个实例是完全等效的? (这被称为值类型对象,如电话号码、地址或屏幕上的点。这些对象除了其状态所暗示的之外没有标识。在后一种情况下,枚举器每次“获取”它时是否返回相同的实例或新创建的相同副本并不重要...此类对象通常是不可变的,它们是相同的,它们保持相同,并且它们的功能相同。
Unless I'm misreading you, your question may be caused by a misunderstanding.. Nothing ever returns an IEnumerable. The first case returns an Enumerator, which implements foreach, allowing you to get instances of MyClass, one at a time. It, (the function return value) is typed as IEnumerable to indicate that it supports the foreach behavior (and a few others)
The second function actually returns a List, which of course also supports IEnumerable (foreach behavior). But it is an actual concrete collection of MyClass Objects, created by the method you called (the second one)
The first method doesn't return any MyClass Objects at all, it returns that enumerator object, which is created by the dotNet framework and coded behind the scenes to instantiate a new MyClass object each time you iterate against it.
EDIT: More detail
A more important distinction is whether or not you want the items to be statefully held in place for you within the class, while you iterate, or whether you want them created for you when you iterate.
Another consideration is.. are the items you wish returned to you already in existence somewhere else? i.e., is this method going to iterate through a set (or filtered subset) of some existing collection? or is it creating the items on the fly? if the latter, does it matter if the item is the exact same instance each time you "get" it?
For objects defined t orepresent things that could be called an entity - ssomething with a defined identity, you probably want successive fetches to return the same instance.
But maybe another instance with the same state is totally equivilent? (This would be called a value type object, like a telephone Number, or an address, or a point on the screen. Such objects have no identity except that implied by their state. In this latter case, it doesn't matter if the enumerator returns the same instance or a newly created identical copy each time you "get" it... Such objects are generally immutable, they are the same, they stay the same, and they function identically.
您可以混合这些建议,您可以实现一个基于泛型的包装类,它采用 IEnumerable 并返回一个新的,在每个下一个上构造一个缓存,并根据需要在进一步的枚举中重用部分缓存。这并不容易,但只会根据需要创建一次对象(实际上仅适用于动态构造对象的迭代器)。最难的部分是确定何时从部分缓存切换回原始枚举器以及如何使其事务性(一致)。
使用经过测试的代码更新:
现在您可以添加一个新的测试来显示它的工作原理:
代码将合并到我的项目中 http://github.com/monoman/MSBuild.NUnit ,稍后也可能出现在 Managed.Commons 项目中
You can mix the suggestions, you can implement an wrapper class, generics-based, that takes the IEnumerable and returns a new one that constructs a cache on each next, and reuses the partial cache as needed on further enumerations. It is not easy, but will create objects (in truth only for Iterators that construct objects on-the-fly) only once and as needed. The hardest part is to be sure when to switch from the partial cache back to the original enumerator and how to make it transactional (consistent).
Update with tested code:
With this you can now add a new Test that shows it works:
Code will be incorporated at my project http://github.com/monoman/MSBuild.NUnit , may later appear in the Managed.Commons project too
然后你需要缓存结果,当你调用迭代它的东西时,IEnumerable总是会重新执行。我倾向于使用:
在另一边授予(以您的示例为例),您可以在此处末尾进行 ToList 调用,它将迭代并创建一个存储的列表,并且yieldResult 仍然是 IEnumerable ,没有问题。
Then you need to cache the result, an IEnumerable is always re-executed when you call something that iterates over it. I tend to use:
Granted on the other side (say for your example) you can have the ToList Call at the end here will iterate and create a list that is stored, and yieldResult will still be an IEnumerable without an issue.