public IEnumerableThrowExeptionForNullParameterYieldReturn(string argument)
{
if(argument==null)
{
throw new ArgumentNullException("argument");
}
yield return 0;
}
[Test]
public void ExceptionWithYieldReturnIsConsumed()
{
Assert.IsNotNull(ThrowExeptionForNullParameterYieldReturn(null));
}
Do you think that this test will pass or fail? It seems quite obvious that it will fail with the ArgumentNullException, before we even get to the first yield return statement, yes? Actually, not at all! The methods using yield return methods are not ordinary ones that just execute when you call them. This test will pass and create a valid IEnumerable object, and none of the code in ThrowExeptionForNullParameterYieldReturn is going to execute. Try yourself, place a break point on the first line of the function, run the test in debugger and notice that the break point is not going to be hit at all.
The code checking the argument is going to run in a lazy fashion, during the first MoveNext() method on the enumerator:
[Test]
[ExpectedException(
ExceptionType = typeof(ArgumentNullException),
ExpectedMessage = "Value cannot be null.\r\nParameter name: argument")]
public void ExceptionWithYieldReturnIsThrownInMoveNext()
{
IEnumerablemyStuff = ThrowExeptionForNullParameterYieldReturn(null);
myStuff.GetEnumerator().MoveNext();
}
The test above passes, so now the exception we expected was really thrown. And when using the traditional approach with creating our own collection, we get the exception before we get hold of the IEnumerable, as expected:
public IEnumerableThrowExeptionForNullParameterList(string argument)
{
if (argument == null)
{
throw new ArgumentNullException("argument");
}
Listresult = new List ();
Result.Add(0);
return result;
}
[Test]
[ExpectedException(
ExceptionType = typeof(ArgumentNullException),
ExpectedMessage = "Value cannot be null.\r\nParameter name: argument")]
public void ExceptionWithListIsThrownAtTheBeginning()
{
ThrowExeptionForNullParameterList(null);
}
The fact that yield return results in a lazy solution is quite important, as it has great impact not only on validating parameters, but also if your function has some side-effects. Imagine logic like this:
IEnumerableGetCustomers()
{
foreach(Customer customer in Customers)
{
if (customer.EligibleOrThrow)
{
yield return customer;
}
}
}
void Pay()
{
IEnumerableeligibleCustomers = GetCustomers();
foreach (Customer eligibleCustomer in eligibleCustomers)
{
customer.Pay(10000);
}
}
So, who did your system pay if one of the customer.EligibleOrThrow throws?
No comments:
Post a Comment