-
Notifications
You must be signed in to change notification settings - Fork 419
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
.Memoize()
Concerns
#889
Comments
For better or worse, this is by-design. We had a long thread about this that's worth reading up to understand the various arguments that were made for and against. If it ever graduates out of the experimental namespace, it should be really called
The disposable pattern is there to make releasing of unmanaged resources deterministic instead of preparing for GC. Over time, it's also been used for just taking advantage of the
Even the iterators returned by standard LINQ operators don't do this: using var e = Enumerable.Range(1, 10).GetEnumerator();
Console.WriteLine(e.MoveNext()); // prints: True
e.Dispose();
Console.WriteLine(e.Current); // prints: 1
Console.WriteLine(e.MoveNext()); // prints: False |
Can you please post a working example demonstrating your first concern? I tried to come up with something based on your description: using System;
using System.Collections.Generic;
using MoreLinq.Experimental;
var foo = Foo().Memoize();
for (var i = 0; i < 2; i++)
{
try
{
Console.WriteLine("Calling GetEnumerator()...");
using var e = foo.GetEnumerator();
Console.WriteLine("Calling MoveNext()...");
e.MoveNext();
Console.WriteLine($"Current = {e.Current}");
Console.WriteLine("Calling MoveNext()...");
e.MoveNext();
}
catch (Exception ex)
{
Console.WriteLine($"! {ex.Message}");
}
}
static IEnumerable<int> Foo()
{
yield return 42;
throw new ApplicationException();
} that I think would show an observable difference, but it doesn't:
In other words, the output is identical for both iterations. |
Nope - poor reading of the code combined with reading the unit test names. I see now that |
Agreed, but under logging scopes, a separate object is created for each scope. That is, I understand some of the comments and concerns in the discussion, however, I would disagree with the conclusion, for a simple reason: If code is written to work for an // nb: poor code here, meant to prove a point
public (TSource loop1, TSource loop2) Process<TSource>(IEnumerable<TSource> source)
{
var memoized = source.Memoized();
return (DoLoop(), DoLoop());
TSource DoLoop()
{
using ((IDisposable)memoized)
return memoized.First();
}
} However, this code will not work if the enumerable passed in is an It would be much better if: Now, I understand your concerns about directly exposing When taking about Clearing the cache in this enumerator doesn't feel, to me, as currently implemented, to fit into either of those categories. Instead, I understand that F# has written |
NB: I consider this a v4.0.0 (or later) issue anyway, so no need to put priority on this. |
Another concerning situation: var list = new List<int>{1,2,3};
var memo = list.Memoize();
var beforeCount = memo.Count();
Assert.That(beforeCount, Is.EqualTo(3));
list.Add(4);
var afterCount = memo.Count();
// fails, because Memoize retrieves the collection iterator
// which uses the value of the list at the time iteration is performed
// this means that data is not truly memoized correctly.
Assert.That(afterCount, Is.EqualTo(beforeCount)); Where this becomes relevant is: var list = new List<int>{1,2,3};
var repeat = list.Repeat(2);
using (var iter = repeat.GetEnumerator())
{
iter.MoveNext();
Assert.That(iter.Current, Is.EqualTo(1));
iter.MoveNext();
Assert.That(iter.Current, Is.EqualTo(2));
iter.MoveNext();
Assert.That(iter.Current, Is.EqualTo(3));
list.Add(4);
// fails, because the list has been modified.
// however, the first pass of the list has been
// completed, so this change to the list should
// not be seen by the `Repeat` operator
iter.MoveNext();
} |
I see two concerns with
.Memoize()
, that I would like to discuss before addressing:Currently, the_error
field is use for both.GetEnumerator()
errors and iteration errors. This is incorrect under the following scenario:Obtain an iterator via.GetEnumerator()
Encounter an error during iteration via.MoveNext()
Attempt to obtain a new iterator.The expectation is that I would be able to get an iterator, and that it would iterate to the exceptionThe current code shares the_error
field, and so I would get an error in.GetEnumerator()
and not be able to iterate to the error.MemoizedEnumerable
incorrectly implements theIDispose
patternObjectDisposedException
ObjectDisposedException
when the object holds an open iteration after being disposed. The.Dispose()
method is treated as a reset rather than an actual disposal; clearing the current cache and starting a new iteration as if.Skip()
had been called on the original sequence..Clear()
method instead of.Dispose()
.@leandromoh I am happy to update the code to correct these issues, but I would like to understand existing thought patterns if either of the above patterns is intended.
CC: @leandromoh
The text was updated successfully, but these errors were encountered: