-
Notifications
You must be signed in to change notification settings - Fork 744
AnyOfConstraint enumerates multiple times #2714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Add AnyOfConstraint to test if item is equal to any of provided values
How does an ICollection solve the problem? It still can have multiple enumeration issues, I believe. Maybe it should be an IList if you are opposed to it simply being an object[]. |
@oznetmaster No,
|
ICollecyion sounds good to me. My understanding on semantics is inline with Joseph’s, but I’ll stand to be corrected here. I’ve added this to 3.10 - if we’re going to change this, I assume we should do so before this feature is released? |
@Dreamescaper Would you be interested in making this change? |
Perhaps it would be simpler to issue the error when we iterate in order to apply the constraint, rather than on construction of the expectation. FWIW, this is the kind of thing uise of separate args can avoid. 😸 |
I find nothing that documents the semantics suggested by @jnm2 . ICollection actually inherits its GetEnumerator from the IEnumerable interface, and adds no new semantics to that. The only real addition is the Count property, which does add finite and countable semantics, but does not apparently imply or restrict the issue of multiple enumerations. IList does not imply any specific order, just that the collection is indexable. It has a different enumerator then ICollection/IEnumerable. |
Again, since the two AnyOf methods only take a params object[], why is there any value in having the AnyOfConstraint take anything besides an object[]? |
I asked @oznetmaster about this and he said he expected it to only be enumerated once for the following code: var someConstraint = Is.AnyOf(weirdEnumerable);
Assert.That(x, someConstraint);
Assert.That(y, someConstraint); Which would rule out that option.
As does IList. The semantics come from the name, the presence of the guaranteed count, and from convention. The word "collection" only makes sense if items are being collected together over time.
I don't object. We can always loosen it. |
Of course I'm keeping in mind that
|
@jnm2 I don't think it's a reasonable demand to put on an In this case, I would say that use of I'd say change it to |
@CharliePoole I was countering @oznetmaster's expectation that I was hoping we'd land on |
ICollection does not have Add, Remove, or Clear. |
And there is no defined expectation that multiple enumerations over an ICollection will work, since it uses the IEnumerable enumerator semantics. I agree that in most cases that should be the case. But again, since this is only being called with an object[] argument, why the continued effort to generalize it? |
Generally, I try to accept the most general argument that's reasonable. I think IEnumerable was unreasonable here but ICollection is not. Using ICollection permits constructing the constraint from a List for example, which is very handy if you generate the accepted values programmatically. As I'm sure you know, there is no formal semantic definition of any interface - unfortunately. Our industry describes syntax in a very structured way but semantics only vaguely. Nevertheless, the distinction between enumerators and collections we have been discussing is something I have heard and read about for years. |
@oznetmaster You're right, but |
That was one of the reasons why I put not-empty guard to Is.AnyOf initially. Personally I know examples of proxied IList, that is reenumerated on each access, however that is very rare situation, and usually very non-intuitive. But yeah, we can make it |
Another option, that I think I like better - to have both |
@Dreamescaper If we have both constructors, we will still have to solve this issue for when the IEnumerable constructor is directly used unless we make it internal or private. |
Seems like we have a misunderstanding. My suggestion is something like this: private readonly object[] _expected;
public AnyOfConstraint(IEnumerable expected)
: this(expected.Cast<object>().ToArray())
{
}
public AnyOfConstraint(object[] expected) : base(expected)
{
// Guards etc
_expected = expected;
} |
I'm sorry, I did misread that! That would be fine if we wanted to be more flexible than |
I already convinced myself that IEnumerable was over-generalized. |
Too bad we can't use |
So what's the decision? Simply use Or use P.S. Don't really matters to me, since I believe that 98% of usages would be via |
Obviously, I prefer the object[] option. |
I could still go either way. |
Great, will send PR soon |
Keep in mind, however, that without additional constructor we won't be able to call |
In my experience, non-generic collections are few and far between. Let's cross that bridge if we ever come to it. |
I wasn't talking about non-generic collections. var myStringList = new List<string> {"A", "B", "C"};
Is.AnyOf(myStringList.ToArray());
//or that
new AnyOfConstraint(myStringList.ToArray()); You can't cast |
Actually, you can. I know how broken array variance is, but so long as it's only read and not written to... |
Ha. Okay, you're right. |
Let's see if it's actually a problem. For example if it's coming from a static field or test parameter, you could just create a new |
I lean toward |
I must have missed something. Why not make AnyOf generic? Sort of:
and other mods accordingly |
I almost suggested it, but we don't want to make the constraint class generic. |
@oznetmaster I suggested that at one point but people felt there would be too many cases where the parameter type couldn't be deduced. |
One can always explicitly add the type if necessary. However, thinking about my usage for this, I would think that it would always be deducible for the cases where I might use it. |
Uh oh!
There was an error while loading. Please reload this page.
@oznetmaster brought to our attention that we are enumerating AnyOfConstraint's expected values multiple times because of this guard statement:
nunit/src/NUnitFramework/framework/Constraints/AnyOfConstraint.cs
Lines 43 to 47 in facc4c8
The IEnumerable contract prevents us from being about to conclude anything from one enumeration about the next enumeration. If we take IEnumerable, we should convert it to an array as its only enumeration and use the array from there on.
I think we should be transparent about the mechanics at play and take an ICollection here instead.
The text was updated successfully, but these errors were encountered: