C# 在生产中使用Fluent断言增强默认.NET LINQ不良异常?
当标准的.NET异常对于stacktrace或其他信息(如 序列包含多个匹配元素 我太懒了,每次在Single之前都不会写if-else语句,这就是为什么不久前我开始在代码中使用这种资产(比如FluentAsserts)结构的原因C# 在生产中使用Fluent断言增强默认.NET LINQ不良异常?,c#,.net,exception,fluent-assertions,C#,.net,Exception,Fluent Assertions,当标准的.NET异常对于stacktrace或其他信息(如 序列包含多个匹配元素 我太懒了,每次在Single之前都不会写if-else语句,这就是为什么不久前我开始在代码中使用这种资产(比如FluentAsserts)结构的原因 var singleItem = itemCollection .Where(i => i.Id = id) .ToArray() .ThrowIfEmpty<Item>(searchCriteria: id)) .ThrowIfMo
var singleItem = itemCollection
.Where(i => i.Id = id)
.ToArray()
.ThrowIfEmpty<Item>(searchCriteria: id))
.ThrowIfMoreThanOne<Item>(searchCriteria: id, dumpItems: true))
.Single();
var singleItem=itemCollection
.其中(i=>i.Id=Id)
.ToArray()
.ThrowIfEmpty(搜索条件:id))
.ThrowIfMoreThanOne(搜索条件:id,dumpItems:true))
.Single();
因此,在执行Single之前,代码会失败,异常更加详细,甚至在异常中包含项。
我不想用它来发明轮子,而是想使用一些现成的生产断言库,这样我就可以编写更可读的代码,比如
var singleItem = itemCollection
.Where(i => i.Id = id)
.ToArray()
.Should().BeNotEmpty().And().HasMoreThanOneElement().For(searchCriteria: id)
//.Otherwise().Throw<MyCustomException>("maybe with some custom message")
.Single();
var singleItem=itemCollection
.其中(i=>i.Id=Id)
.ToArray()
.Should().BeNotEmpty()和().hasMorethaneElement().For(搜索条件:id)
//.Others().Throw(“可能带有一些自定义消息”)
.Single();
与FluentAssertions一样,此库是为测试而开发的,而不是为生产而开发的
是否有任何关于生产准备解决方案的建议
可能相关问题:
由于我没有在互联网上找到一些东西,也没有从人们那里得到一些解决方案,所以我在FluentAsserts的启发下发明了我的“轮子” 免责声明:未在生产中进行测试,但进行了一些本地测试和性能测量 背后的想法是使代码/LINQ抛出的异常更加详细
// may throw
// Sequence contains more than one matching element
// or
// Sequence contains no matching element
var single = source.Single(x => x.Value == someEnumValue);
在这种情况下,只有堆栈跟踪可能有助于识别发生时的行,但如果异常经过少数服务(如WCF)层,堆栈跟踪可能会丢失或被覆盖。
通常,您会使异常像这样更加详细
更多
我们可能会看到可以使用相同的异常消息模式,因此(对我来说)显而易见的解决方案是将其包装到一些可重用的通用代码中,并封装这些冗长的内容。所以这个例子看起来像
// throw generic but verbose InvalidOperationException like
// Sequence of type SomeType contains no matching element with the search criteria Value=SomeEnum.Value
var single = source
.AllowVerboseException()
.WithSearchParams(someEnumValue)
.Single(x => x.Value == someEnumValue);
// or CustomException
var single = source
.AllowVerboseException()
.WithSearchParams(someEnumValue)
.IfEmpty().Throws<CustomException>()
.IfMoreThanOne().Throws<CustomException>()
.Single(x => x.Value == someEnumValue);
// or CustomException with custom messages
var single = source
.AllowVerboseException()
.WithSearchParams(someEnumValue)
.IfEmpty().Throws<CustomException>("Found nothing in the source for " + someEnumValue)
.IfMoreThanOne().Throws<CustomException>("Found more than one in the source for " + someEnumValue)
.Single(x => x.Value == someEnumValue);
//抛出泛型但详细的InvalidOperationException,如
//SomeType类型的序列不包含搜索条件值为SomeEnum.Value的匹配元素
var单=源
.AllowVerboseException()
.WithSearchParams(someEnumValue)
.Single(x=>x.Value==someEnumValue);
//或自定义例外
var单=源
.AllowVerboseException()
.WithSearchParams(someEnumValue)
.IfEmpty().Throws()
.ifmore().Throws()
.Single(x=>x.Value==someEnumValue);
//或带有自定义消息的自定义异常
var单=源
.AllowVerboseException()
.WithSearchParams(someEnumValue)
.IfEmpty().Throws(“在“+someEnumValue”的源中找不到任何内容)
.ifmorethane().Throws(“在“+someEnumValue”的源中找到多个)
.Single(x=>x.Value==someEnumValue);
fluent断言解决方案允许
- 倾销项目(之前必须列举顺序)
- 自定义消息的延迟加载(通过传递Func而不是字符串)
- 在不调用单个/第一个方法的情况下验证假设(完全替换if-else)(毕竟调用Verify方法.IfEmpty().Throws和/或.IfMoreThanOne().Throws)
- 处理任何案件
private const int MoreThanOne = 2;
...
public T SingleOrDefault(Func<T, bool> predicate = null)
{
if (predicate != null)
this.sequence = this.sequence.Where(predicate);
return this.Get(Only.Single | Only.Default);
}
...
private T Get(Only only)
{
// the main trip and probably performance downgrade
// the logic takes first 2 elements to then check IfMoreThanOne
// it might be critical in DB queries but might be not
var items = this.sequence.Take(MoreThanOne).ToList();
switch (items.Count)
{
case 1:
case MoreThanOne when only.HasFlag(Only.First):
var first = items.First();
this.Dispose();
return first;
case 0 when only.HasFlag(Only.Default):
this.Dispose();
return default(T);
}
if (this.ifEmptyExceptionFunc == null) this.ifEmptyExceptionFunc = DefaultExceptionFunc;
if (this.ifMoreThanOneExceptionFunc == null) this.ifMoreThanOneExceptionFunc = DefaultExceptionFunc;
this.Verify(() => items.Count);
throw new NotSupportedException("Should not reach this code");
}
private void Verify(Func<int> getItemCount)
{
var itemCount = getItemCount.InitLazy();
ExceptionFunc exceptionFunc = null;
string message = null;
if (this.ifEmptyExceptionFunc != null && itemCount.Value == 0)
{
message = Messages.Elements.NoOne;
exceptionFunc = this.ifEmptyExceptionFunc;
}
else if (this.ifMoreThanOneExceptionFunc != null && itemCount.Value > 1)
{
message = Messages.Elements.MoreThanOne;
exceptionFunc = this.ifMoreThanOneExceptionFunc;
}
else if (this.ifAnyExceptionFunc != null && itemCount.Value > 0)
{
message = Messages.Elements.Some;
exceptionFunc = this.ifAnyExceptionFunc;
}
if (exceptionFunc == null)
return;
message = string.Format(Messages.BeginningFormat, this.typeNameOverride ?? typeof(T).
this.searchCriteria = this.searchCriteria ?? this.searchCriteriaFunc?.Invoke();
if (!string.IsNullOrWhiteSpace(this.searchCriteria))
{
message += $" with the search criteria {this.searchCriteria}";
}
if (this.dumpItemFunc != null)
{
message += ". Items: " + this.dumpItemFunc();
}
try
{
throw exceptionFunc(message);
}
finally
{
this.Dispose();
}
}
private const int不止一个=2;
...
公共T SingleOrDefault(Func谓词=null)
{
if(谓词!=null)
this.sequence=this.sequence.Where(谓词);
返回此.Get(Only.Single | Only.Default);
}
...
私人T Get(仅限)
{
//主行程和可能的性能降级
//逻辑先取前2个元素,然后检查多个元素
//它在数据库查询中可能很重要,但可能不是
var items=this.sequence.Take(不止一个).ToList();
开关(项目数)
{
案例1:
仅当.HasFlag(仅.First)时,情况不止一个:
var first=items.first();
这个。Dispose();
先返回;
仅当.HasFlag(仅.Default)时为案例0:
这个。Dispose();
返回默认值(T);
}
如果(this.ifEmptyExceptionFunc==null)this.ifEmptyExceptionFunc=DefaultExceptionFunc;
如果(this.ifMoreThanOneExceptionFunc==null)this.ifMoreThanOneExceptionFunc=DefaultExceptionFunc;
验证(()=>items.Count);
抛出新的NotSupportedException(“不应到达此代码”);
}
私有无效验证(Func getItemCount)
{
var itemCount=getItemCount.InitLazy();
ExceptionFunc ExceptionFunc=null;
字符串消息=null;
if(this.ifEmptyExceptionFunc!=null&&itemCount.Value==0)
{
message=Messages.Elements.NoOne;
exceptionFunc=this.ifEmptyExceptionFunc;
}
else if(this.ifMoreThanOneExceptionFunc!=null&&itemCount.Value>1)
{
message=Messages.Elements.more;
exceptionFunc=this.ifmorethaneoExceptionFunc;
}
else if(this.ifAnyExceptionFunc!=null&&itemCount.Value>0)
{
message=Messages.Elements.Some;
exceptionFunc=this.ifAnyExceptionFunc;
}
if(exceptionFunc==null)
回来
message=string.Format(Messages.beginingformat,this.typeNameOverride??typeof(T)。
this.searchCriteria=this.searchCriteria??this.searchCriteriaFunc?.Invoke();
如果(!string.IsNullOrWhiteSpace(this.searchCriteria))
{
带有搜索条件{this.searchCriteria}的消息+=$”;
}
如果(this.dumpItemFunc!=null)
{
message+=”.Items:“+this.dumpItemFunc();
}
尝试
{
抛出异常函数(消息);
}
最后
{
这个。Dispose();
}
}
当您仅使用Single()
时,您肯定希望
private const int MoreThanOne = 2;
...
public T SingleOrDefault(Func<T, bool> predicate = null)
{
if (predicate != null)
this.sequence = this.sequence.Where(predicate);
return this.Get(Only.Single | Only.Default);
}
...
private T Get(Only only)
{
// the main trip and probably performance downgrade
// the logic takes first 2 elements to then check IfMoreThanOne
// it might be critical in DB queries but might be not
var items = this.sequence.Take(MoreThanOne).ToList();
switch (items.Count)
{
case 1:
case MoreThanOne when only.HasFlag(Only.First):
var first = items.First();
this.Dispose();
return first;
case 0 when only.HasFlag(Only.Default):
this.Dispose();
return default(T);
}
if (this.ifEmptyExceptionFunc == null) this.ifEmptyExceptionFunc = DefaultExceptionFunc;
if (this.ifMoreThanOneExceptionFunc == null) this.ifMoreThanOneExceptionFunc = DefaultExceptionFunc;
this.Verify(() => items.Count);
throw new NotSupportedException("Should not reach this code");
}
private void Verify(Func<int> getItemCount)
{
var itemCount = getItemCount.InitLazy();
ExceptionFunc exceptionFunc = null;
string message = null;
if (this.ifEmptyExceptionFunc != null && itemCount.Value == 0)
{
message = Messages.Elements.NoOne;
exceptionFunc = this.ifEmptyExceptionFunc;
}
else if (this.ifMoreThanOneExceptionFunc != null && itemCount.Value > 1)
{
message = Messages.Elements.MoreThanOne;
exceptionFunc = this.ifMoreThanOneExceptionFunc;
}
else if (this.ifAnyExceptionFunc != null && itemCount.Value > 0)
{
message = Messages.Elements.Some;
exceptionFunc = this.ifAnyExceptionFunc;
}
if (exceptionFunc == null)
return;
message = string.Format(Messages.BeginningFormat, this.typeNameOverride ?? typeof(T).
this.searchCriteria = this.searchCriteria ?? this.searchCriteriaFunc?.Invoke();
if (!string.IsNullOrWhiteSpace(this.searchCriteria))
{
message += $" with the search criteria {this.searchCriteria}";
}
if (this.dumpItemFunc != null)
{
message += ". Items: " + this.dumpItemFunc();
}
try
{
throw exceptionFunc(message);
}
finally
{
this.Dispose();
}
}