C# 使用Single和SingleOrDefault抛出更简洁的异常
在C# 使用Single和SingleOrDefault抛出更简洁的异常,c#,.net,linq,exception,design-patterns,C#,.net,Linq,Exception,Design Patterns,在IEnumerable上调用Single或SingleOrDefault,并且它有多个结果时,会抛出InvalidOperationException 虽然异常的实际消息非常具有描述性,但编写一个catch只处理Single/SingleOrDefault调用失败的情况是有问题的 public virtual Fee GetFeeByPromoCode(string promoCode) { try { return _fees.SingleOrDefault(
IEnumerable
上调用Single
或SingleOrDefault
,并且它有多个结果时,会抛出InvalidOperationException
虽然异常的实际消息非常具有描述性,但编写一个catch只处理Single
/SingleOrDefault
调用失败的情况是有问题的
public virtual Fee GetFeeByPromoCode(string promoCode)
{
try
{
return _fees.SingleOrDefault(f => f.IsPromoCodeValid(promoCode));
}
catch (InvalidOperationException)
{
throw new TooManyFeesException();
}
}
在这种情况下,如果IsPromoCodeValid
也抛出invalidoOperationException
,则捕获正在处理的内容变得不明确
我可以检查异常消息,但我希望避免这种情况,因为我发现根据异常消息处理代码很麻烦
我当前的SingleOrDefault
替代方案如下所示:
public virtual Fee GetFeeByPromoCode(string promoCode)
{
var fees = _fees.Where(f => f.IsPromoCodeValid(promoCode)).ToList();
if (fees.Count > 1)
{
throw new InvalidFeeSetupException();
}
return fees.FirstOrDefault();
}
但是,与上面的代码相比,此代码不太明显。此外,与使用SingleOrDefault
相比,此代码生成的查询效率较低(如果使用启用linq的ORM)
我还可以对第二个示例进行Take(2)
优化,但这进一步混淆了代码的意图
有没有一种方法可以做到这一点,而不必为
IEnumerable
和IQueryable
编写自己的扩展名?这样可以解决问题吗
public virtual Fee GetFeeByPromoCode(string promoCode)
{
try
{
return _fees.SingleOrDefault(f =>
{
try
{
return f.IsPromoCodeValid(promoCode);
}
catch(InvalidOperationException)
{
throw new PromoCodeException();
}
});
}
catch (InvalidOperationException)
{
throw new TooManyFeesException();
}
}
无效操作异常
相当普遍。任何被访问的属性(甚至堆栈中更深的属性)都可能引发此异常。因此,一种方法是滚动您自己的异常和扩展方法。例如:
static class EnumerableExtensions
{
public static TSource ExactlyOneOrZero<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
if (source == null) { throw new ArgumentNullException("source"); }
if (predicate == null) { throw new ArgumentNullException("predicate"); }
IEnumerable<TSource> matchingItems = source.Where(predicate);
IReadOnlyList<TSource> limitedMatchingItems = matchingItems.Take(2).ToList();
int matchedItemCount = limitedMatchingItems.Count;
switch (matchedItemCount)
{
case 0: return default(TSource);
case 1: return limitedMatchingItems[0]; // Or Single()
default: throw new TooManyMatchesException();
}
}
}
class TooManyMatchesException : Exception { /* Don't forget to implement this properly. */ }
另一种方法是使用TryGet…
-模式,但它不是很干净。即使没有匹配的元素,TryGetSingle
也会返回true。你可以用EnUM(有效/无效)替换布尔,但是我将把它留给读者看它是否可读。 < P>我把FiST()/Sunle()/SunLeReOffer()视为一种断言。
i、 如果你使用它们,你不想捕捉异常。您的数据存在严重错误,应将其视为严重错误处理
如果模型中有多个结果是正常的,则不要使用异常来验证它
从这个角度来看,我不认为Take(2)版本不那么明显。我只是想能够修改异常消息,我使用带有普通字符串参数的扩展来完成这一点(请参阅下面的第一个扩展方法) 你的解决方案我使用了一个通用的扩展,使它很好和干净。我传递了要作为泛型类型抛出的异常类型(请参见下面的第二个扩展方法) 我反编译了System.Linq.Enumerable库,复制了它们的代码,修改了要抛出的异常消息和异常类型,我们开始吧 新的linq扩展:
public static class LinqExtentions
{
// Extension method 1 : Just to change the message for the "more than one" exception
public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source, string moreThanOneMatchMessage = "MoreThanOneMatch")
{
if (source == null)
{
throw new ArgumentNullException("source");
}
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
switch (list.Count)
{
case 0:
return default(TSource);
case 1:
return list[0];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (!enumerator.MoveNext())
{
return default(TSource);
}
TSource current = enumerator.Current;
if (!enumerator.MoveNext())
{
return current;
}
}
}
// I Changed this line below from their code - moreThanOneMatchMessage as parameter
// It was : throw Error.MoreThanOneElement(); in other words it was throw new InvalidOperationException("MoreThanOneMatch");
throw new InvalidOperationException(moreThanOneMatchMessage);
}
// Extension method 2 : Change the Exception Type to be thrown and the message
public static TSource SingleOrDefault<TSource, TMoreThanOnceExceptionType>(this IEnumerable<TSource> source, string noElementsMessage = "NoElements", string moreThanOneMatchMessage = "MoreThanOneMatch")
where TMoreThanOnceExceptionType : Exception
{
if (source == null)
{
throw new ArgumentNullException("source");
}
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
switch (list.Count)
{
case 0:
return default(TSource);
case 1:
return list[0];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (!enumerator.MoveNext())
{
return default(TSource);
}
TSource current = enumerator.Current;
if (!enumerator.MoveNext())
{
return current;
}
}
}
// Changes this line below to throw dynamic exception type.
// It was : throw Error.MoreThanOneElement(); in other words it was throw new InvalidOperationException("MoreThanOneMatch");
// Yes some believe the Activator can slow down code, If you use a DI Framework and register your exception type this should not be the case
throw (TMoreThanOnceExceptionType)Activator.CreateInstance(typeof(TMoreThanOnceExceptionType), moreThanOneMatchMessage);
}
}
公共静态类linqextensions
{
//扩展方法1:仅更改“多个”异常的消息
公共静态TSource SingleOrDefault(此IEnumerable源,字符串moreThanOneMatchMessage=“MoreThanOneMatch”)
{
if(source==null)
{
抛出新的ArgumentNullException(“源”);
}
IList list=源作为IList;
如果(列表!=null)
{
开关(list.Count)
{
案例0:
返回默认值(TSource);
案例1:
返回列表[0];
}
}
其他的
{
使用(IEnumerator enumerator=source.GetEnumerator())
{
如果(!enumerator.MoveNext())
{
返回默认值(TSource);
}
TSource current=枚举数.current;
如果(!enumerator.MoveNext())
{
回流;
}
}
}
//我在下面的代码中更改了这一行,并将多条匹配消息作为参数
//它是:throw Error.MoreThanOneElement();换句话说,它是throw new invalidoOperationException(“MoreThanOneMatch”);
抛出新的InvalidOperationException(不止一条匹配消息);
}
//扩展方法2:更改要引发的异常类型和消息
公共静态TSource SingleOrDefault(此IEnumerable源,字符串noElementsMessage=“NoElements”,字符串moreThanOneMatchMessage=“MoreThanOneMatch”)
其中TMoreThanOnceExceptionType:Exception
{
if(source==null)
{
抛出新的ArgumentNullException(“源”);
}
IList list=源作为IList;
如果(列表!=null)
{
开关(list.Count)
{
案例0:
返回默认值(TSource);
案例1:
返回列表[0];
}
}
其他的
{
使用(IEnumerator enumerator=source.GetEnumerator())
{
如果(!enumerator.MoveNext())
{
返回默认值(TSource);
}
TSource current=枚举数.current;
如果(!enumerator.MoveNext())
{
回流;
}
}
}
//将下面的此行更改为引发动态异常类型。
//它是:throw Error.MoreThanOneElement();换句话说,它是throw new invalidoOperationException(“MoreThanOneMatch”);
//是的,有些人认为激活器会减慢代码的速度,如果您使用DI框架并注册您的异常类型,则不应如此
throw(TMoreThanOnceExceptionType)Activator.CreateInstance(typeof(TMoreThanOnceExceptionType),morethaneMatchMessage);
}
}
好的,下面是使用代码:
public static void TestMethod(string promoCode)
{
List<Fee> promoCodes = new List<Fee>();
// Add promo codes in for example
// moreThanOneMatchMessage = "Duplicate Promo codes detected" and retur null for no codes
promoCodes
.Where(f => f.IsPromoCodeValid(promoCode))
.SingleOrDefault(moreThanOneMatchMessage: "Duplicate Promo codes detected");
// OR noElementsMessage = "There is no Promotion codes configured" and moreThanOneMatchMessage = "Duplicate Promo codes!!"
// This extention was not included in the code section, wanted to keep response small however show this extention, same concept as SingleOrDefault extention
promoCodes
.Where(f => f.IsPromoCodeValid(promoCode))
.Single(moreThanOneMatchMessage: "Duplicate Promo codes!!", noElementsMessage: "There is no Promotion codes configured");
try
{
// Lets Customeze the exception type, TooManyFeesException thrown with a message "Duplicate Promo codes!!"
// AND if there are no items a InvlaidArgumentException with message "There is no Promotion codes configured"
promoCodes
.Where(f => f.IsPromoCodeValid(promoCode))
.SingleOrDefault<Fee, TooManyFeesException>("There is no Promotion codes configured", "Duplicate Promo codes!!");
}
catch (TooManyFeesException tmte)
{
//catching you specific exception here
}
}
publicstaticvoidtestmethod(字符串代码)
{
列表代码=新列表();
//添加促销代码
public static void TestMethod(string promoCode)
{
List<Fee> promoCodes = new List<Fee>();
// Add promo codes in for example
// moreThanOneMatchMessage = "Duplicate Promo codes detected" and retur null for no codes
promoCodes
.Where(f => f.IsPromoCodeValid(promoCode))
.SingleOrDefault(moreThanOneMatchMessage: "Duplicate Promo codes detected");
// OR noElementsMessage = "There is no Promotion codes configured" and moreThanOneMatchMessage = "Duplicate Promo codes!!"
// This extention was not included in the code section, wanted to keep response small however show this extention, same concept as SingleOrDefault extention
promoCodes
.Where(f => f.IsPromoCodeValid(promoCode))
.Single(moreThanOneMatchMessage: "Duplicate Promo codes!!", noElementsMessage: "There is no Promotion codes configured");
try
{
// Lets Customeze the exception type, TooManyFeesException thrown with a message "Duplicate Promo codes!!"
// AND if there are no items a InvlaidArgumentException with message "There is no Promotion codes configured"
promoCodes
.Where(f => f.IsPromoCodeValid(promoCode))
.SingleOrDefault<Fee, TooManyFeesException>("There is no Promotion codes configured", "Duplicate Promo codes!!");
}
catch (TooManyFeesException tmte)
{
//catching you specific exception here
}
}