max*_*llb 3 c# language-features language-design
假设您正在使用三元运算符或空合并运算符或嵌套的if-else语句来选择对象的赋值.现在假设在条件语句中,您对昂贵或易失性操作进行了评估,要求将结果放入临时变量中,捕获其状态,以便可以对其进行比较,然后进行可能的分配.
一个语言(如C#)如何实现一个新的逻辑运算符来处理这种情况?应该是?在C#中是否存在处理此案例的现有方法?其他语言?
例如,当我们假设我们正在寻找直接比较时,已经克服了一些降低三元或零合并算子的冗长度的情况.请参阅使用Null Coalescing运算符的唯一方法,特别是关于如何扩展运算符的使用以支持的讨论String.IsNullOrEmpty(string).注意Jon Skeet如何使用PartialComparerfrom MiscUtil,将0s 重新格式化为nulls,
为什么这可能是必要的?那么,看看我们如何为没有任何快捷方式的复杂对象编写比较方法(引用的讨论中的示例):
public static int Compare( Person p1, Person p2 )
{
return ( (result = Compare( p1.Age, p2.Age )) != 0 ) ? result
: ( (result = Compare( p1.Name, p2.Name )) != 0 ) ? result
: Compare( p1.Salary, p2.Salary );
}
Run Code Online (Sandbox Code Playgroud)
Jon Skeet写了一个新的比较来回避平等案例.这允许通过编写一个返回null的新特定方法来扩展表达式,允许我们使用null合并运算符:
return PartialComparer.Compare(p1.Age, p2.Age)
?? PartialComparer.Compare(p1.Name, p2.Name)
?? PartialComparer.Compare(p1.Salary, p2.Salary)
?? 0;
Run Code Online (Sandbox Code Playgroud)
空合并运算符更具可读性,因为它有两个边,而不是三个边.布尔条件子句分为一个方法,在这种情况下返回null表达式必须继续.
如果我们可以更容易地将条件置于线上,那么上面的表达式会是什么样的?获取PartialComparer.Compare返回的表达式null,并将其放在一个新的三元表达式中,该表达式允许我们使用隐式临时变量来使用左侧表达式的求值value:
return Compare( p1.Age, p2.Age ) unless value == 0
: Compare( p1.Name, p2.Name ) unless value == 0
: Compare( p1.Salary, p2.Salary );
Run Code Online (Sandbox Code Playgroud)
表达式的基本"流程"将是:
表达式 A除非boolean B,在这种情况下表达式为 C
我认为这更像是一个短路的倒三元运算符,而不是一个重载的比较运算符.
(value == null).(String.IsNullOrEmpty(value)).我个人避免操作员的短路,只是让方法链接它:
public static int CompareChain<T>(this int previous, T a, T b)
{
if (previous != 0)
return previous;
return Comparer<T>.Default.Compare(a,b);
}
Run Code Online (Sandbox Code Playgroud)
像这样使用:
int a = 0, b = 2;
string x = "foo", y = "bar";
return a.Compare(b).CompareChain(x,y);
Run Code Online (Sandbox Code Playgroud)
可以通过JIT内联,因此它可以像语言中内置的短路一样执行,而不会产生更多的复杂性.
在回答您询问上述"结构"是否可以应用于不仅仅是比较时,可以通过选择是否继续或不可由用户进行控制来实现.这本质上更复杂,但操作更灵活,因此这是不可避免的.
public static T ElseIf<T>(
this T previous,
Func<T,bool> isOK
Func<T> candidate)
{
if (previous != null && isOK(previous))
return previous;
return candidate();
}
Run Code Online (Sandbox Code Playgroud)
然后像这样使用
Connection bestConnection = server1.GetConnection()
.ElseIf(IsOk, server2.GetConnection)
.ElseIf(IsOk, server3.GetConnection)
.ElseIf(IsOk, () => null);
Run Code Online (Sandbox Code Playgroud)
这是最大的灵活性,因为您可以在任何阶段更改IsOk检查并且完全是懒惰的.对于OK检查在每种情况下都是相同的情况,您可以像这样简化并完全避免扩展方法.
public static T ElseIf<T>(
Func<T,bool> isOK
IEnumerable<Func<T>[] candidates)
{
foreach (var candidate in candidates)
{
var t = candidate();
if (isOK(t))
return t;
}
throw new ArgumentException("none were acceptable");
}
Run Code Online (Sandbox Code Playgroud)
您可以使用linq执行此操作,但这样可以提供一个很好的错误消息并允许此操作
public static T ElseIf<T>(
Func<T,bool> isOK
params Func<T>[] candidates)
{
return ElseIf<T>(isOK, (IEnumerable<Func<T>>)candidates);
}
Run Code Online (Sandbox Code Playgroud)
样式导致可读的代码如下:
var bestConnection = ElseIf(IsOk,
server1.GetConnection,
server2.GetConnection,
server3.GetConnection);
Run Code Online (Sandbox Code Playgroud)
如果要允许默认值,则:
public static T ElseIfOrDefault<T>(
Func<T,bool> isOK
IEnumerable<Func<T>>[] candidates)
{
foreach (var candidate in candidates)
{
var t = candidate();
if (isOK(t))
return t;
}
return default(T);
}
Run Code Online (Sandbox Code Playgroud)
显然,以上所有内容都可以使用lambdas编写,因此您的具体示例如下:
var bestConnection = ElseIfOrDefault(
c => c != null && !(c.IsBusy || c.IsFull),
server1.GetConnection,
server2.GetConnection,
server3.GetConnection);
Run Code Online (Sandbox Code Playgroud)