仅实现单一枚举的最佳方式 Moq IEnumerable
Best way to implement single enumeration only Moq IEnumerable
在重构项目时,我 运行 遇到了一个问题,即 IEnumerable<T>
方法的使用者假定返回的枚举可以被枚举多次。这不再正确,应用程序抛出 NotSupportedException
:“此 IEnumerable 仅支持单个枚举。”)。有很多单元测试(使用 XUnit 和 Moq),但他们没有抓住这一点,因为他们会使用 List<T>
作为 IEnumerable 实现。现在我想让单元测试暴露这个错误。
我想到了以下实现,它似乎按我想要的方式工作。
我的问题:
- 一般情况下是否有更简单的方法(c#,不管最小起订量)?
- 是否有一些更简单的内置方法可以专门在 Moq 中执行此操作?
public class DataTransferObject
{
public string Title { get; set; }
}
public interface IDemoClient
{
IEnumerable<DataTransferObject> GetData();
}
public class OnceEnumerator<T> : IEnumerator<T>
{
private IList<T> _data;
private int position = -1;
internal OnceEnumerator(IList<T> data)
{
_data = data;
}
public T Current => _data[position];
object IEnumerator.Current => _data[position];
public void Dispose()
{
}
public bool MoveNext()
{
position++;
return (position < _data.Count);
}
public void Reset()
{
throw new NotSupportedException("Reset(): Only a single enumeration is supported by this IEnumerable.");
}
}
public class OnceEnumerable<T> : IEnumerable<T>
{
private OnceEnumerator<T> _enum;
private bool _enumReturned = false;
public OnceEnumerable(IList<T> data)
{
_enum = new OnceEnumerator<T>(data);
}
public IEnumerator<T> GetEnumerator()
{
if (_enumReturned)
{
throw new NotSupportedException("GetEnumerator(): Only a single enumeration is supported by this IEnumerable.");
}
else
{
_enumReturned = true;
return _enum;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
}
public class UnitTest1
{
[Fact]
public void Test1()
{
var client = new Mock<IDemoClient>();
client.Setup(c => c.GetData()).Returns(new OnceEnumerable<DataTransferObject>(
new List<DataTransferObject> {
new DataTransferObject { Title = "foo" },
new DataTransferObject { Title = "bar" }
}));
var data = client.Object.GetData();
// enumerate once...
if (data.Any(x => x.Title == "biff"))
{
Console.WriteLine("found biff");
}
// I get the behaviour I want:
// System.NotSupportedException : GetEnumerator(): Only a single enumeration is supported by this IEnumerable.
if (data.Any(x => x.Title == "boff"))
{
Console.WriteLine("found boff");
}
}
}
没有内置这样的功能。但是如果您使用自动生成的迭代器不支持 IEnumerator.Reset
方法这一事实,您的代码可以得到简化:
public class OnceEnumerable<T> : IEnumerable<T>
{
private readonly IEnumerable<T> source;
private bool isEnumeratorRequested;
public OnceEnumerable(IEnumerable<T> source)
{
this.source = source;
}
public IEnumerator<T> GetEnumerator()
{
if (isEnumeratorRequested)
throw new NotSupportedException();
isEnumeratorRequested = true;
return EnumerateSource().GetEnumerator();
IEnumerable<T> EnumerateSource() // You cannot reset this
{
foreach (var item in source)
yield return item;
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
我还会创建一个扩展方法来轻松地将任何可枚举转换为一次可枚举:
public static IEnumerable<T> AsOnceEnumerable<T>(this IEnumerable<T> source) =>
new OnceEnumerable<T>(source);
用法:
dtos.AsOnceEnumerable()
在重构项目时,我 运行 遇到了一个问题,即 IEnumerable<T>
方法的使用者假定返回的枚举可以被枚举多次。这不再正确,应用程序抛出 NotSupportedException
:“此 IEnumerable 仅支持单个枚举。”)。有很多单元测试(使用 XUnit 和 Moq),但他们没有抓住这一点,因为他们会使用 List<T>
作为 IEnumerable 实现。现在我想让单元测试暴露这个错误。
我想到了以下实现,它似乎按我想要的方式工作。
我的问题:
- 一般情况下是否有更简单的方法(c#,不管最小起订量)?
- 是否有一些更简单的内置方法可以专门在 Moq 中执行此操作?
public class DataTransferObject
{
public string Title { get; set; }
}
public interface IDemoClient
{
IEnumerable<DataTransferObject> GetData();
}
public class OnceEnumerator<T> : IEnumerator<T>
{
private IList<T> _data;
private int position = -1;
internal OnceEnumerator(IList<T> data)
{
_data = data;
}
public T Current => _data[position];
object IEnumerator.Current => _data[position];
public void Dispose()
{
}
public bool MoveNext()
{
position++;
return (position < _data.Count);
}
public void Reset()
{
throw new NotSupportedException("Reset(): Only a single enumeration is supported by this IEnumerable.");
}
}
public class OnceEnumerable<T> : IEnumerable<T>
{
private OnceEnumerator<T> _enum;
private bool _enumReturned = false;
public OnceEnumerable(IList<T> data)
{
_enum = new OnceEnumerator<T>(data);
}
public IEnumerator<T> GetEnumerator()
{
if (_enumReturned)
{
throw new NotSupportedException("GetEnumerator(): Only a single enumeration is supported by this IEnumerable.");
}
else
{
_enumReturned = true;
return _enum;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
}
public class UnitTest1
{
[Fact]
public void Test1()
{
var client = new Mock<IDemoClient>();
client.Setup(c => c.GetData()).Returns(new OnceEnumerable<DataTransferObject>(
new List<DataTransferObject> {
new DataTransferObject { Title = "foo" },
new DataTransferObject { Title = "bar" }
}));
var data = client.Object.GetData();
// enumerate once...
if (data.Any(x => x.Title == "biff"))
{
Console.WriteLine("found biff");
}
// I get the behaviour I want:
// System.NotSupportedException : GetEnumerator(): Only a single enumeration is supported by this IEnumerable.
if (data.Any(x => x.Title == "boff"))
{
Console.WriteLine("found boff");
}
}
}
没有内置这样的功能。但是如果您使用自动生成的迭代器不支持 IEnumerator.Reset
方法这一事实,您的代码可以得到简化:
public class OnceEnumerable<T> : IEnumerable<T>
{
private readonly IEnumerable<T> source;
private bool isEnumeratorRequested;
public OnceEnumerable(IEnumerable<T> source)
{
this.source = source;
}
public IEnumerator<T> GetEnumerator()
{
if (isEnumeratorRequested)
throw new NotSupportedException();
isEnumeratorRequested = true;
return EnumerateSource().GetEnumerator();
IEnumerable<T> EnumerateSource() // You cannot reset this
{
foreach (var item in source)
yield return item;
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
我还会创建一个扩展方法来轻松地将任何可枚举转换为一次可枚举:
public static IEnumerable<T> AsOnceEnumerable<T>(this IEnumerable<T> source) =>
new OnceEnumerable<T>(source);
用法:
dtos.AsOnceEnumerable()