단락 연산자를 해 || &&는 nullable 부울에 대해 존재합니까? RuntimeBinder는 때때로 그렇게 생각합니다.
조건부 논리 연산자 ||
및 &&
단락 논리 연산자라고도 하는 C # 언어 사양을 읽었습니다 . 나에게 nullable booleans, 즉 피연산자 유형 Nullable<bool>
(또한 작성 됨 bool?
)에 대해 존재하는지 명확하지 않은 것처럼 보였으 므로 비 동적 유형으로 시도했습니다.
bool a = true;
bool? b = null;
bool? xxxx = b || a; // compile-time error, || can't be applied to these types
그것은 질문을 해결하는 것처럼 보였습니다 (사양을 명확하게 이해할 수는 없지만 Visual C # 컴파일러의 구현이 정확하다고 가정하면 이제 알았습니다).
하지만 dynamic
바인딩도 해보고 싶었습니다 . 그래서 대신 이것을 시도했습니다.
static class Program
{
static dynamic A
{
get
{
Console.WriteLine("'A' evaluated");
return true;
}
}
static dynamic B
{
get
{
Console.WriteLine("'B' evaluated");
return null;
}
}
static void Main()
{
dynamic x = A | B;
Console.WriteLine((object)x);
dynamic y = A & B;
Console.WriteLine((object)y);
dynamic xx = A || B;
Console.WriteLine((object)xx);
dynamic yy = A && B;
Console.WriteLine((object)yy);
}
}
놀라운 결과는 예외없이 실행된다는 것입니다.
Well, x
and y
are not surprising, their declarations lead to both properties being retrieved, and the resulting values are as expected, x
is true
and y
is null
.
But the evaluation for xx
of A || B
lead to no binding-time exception, and only the property A
was read, not B
. Why does this happen? As you can tell, we could change the B
getter to return a crazy object, like "Hello world"
, and xx
would still evaluate to true
without binding-problems...
Evaluating A && B
(for yy
) also leads to no binding-time error. And here both properties are retrieved, of course. Why is this allowed by the run-time binder? If the returned object from B
is changed to a "bad" object (like a string
), a binding exception does occur.
Is this correct behavior? (How can you infer that from the spec?)
If you try B
as first operand, both B || A
and B && A
give runtime binder exception (B | A
and B & A
work fine as everything is normal with non-short-circuiting operators |
and &
).
(Tried with C# compiler of Visual Studio 2013, and runtime version .NET 4.5.2.)
First of all, thanks for pointing out that the spec isn't clear on the non-dynamic nullable-bool case. I will fix that in a future version. The compiler's behavior is the intended behavior; &&
and ||
are not supposed to work on nullable bools.
The dynamic binder does not seem to implement this restriction, though. Instead, it binds the component operations separately: the &
/|
and the ?:
. Thus it's able to muddle through if the first operand happens to be true
or false
(which are boolean values and thus allowed as the first operand of ?:
), but if you give null
as the first operand (e.g. if you try B && A
in the example above), you do get a runtime binding exception.
If you think about it, you can see why we implemented dynamic &&
and ||
this way instead of as one big dynamic operation: dynamic operations are bound at runtime after their operands are evaluated, so that the binding can be based on the runtime types of the results of those evaluations. But such eager evaluation defeats the purpose of short-circuiting operators! So instead, the generated code for dynamic &&
and ||
breaks the evaluation up into pieces and will proceed as follows:
- Evaluate the left operand (let's call the result
x
) - Try to turn it into a
bool
via implicit conversion, or thetrue
orfalse
operators (fail if unable) - Use
x
as the condition in a?:
operation - In the true branch, use
x
as a result - In the false branch, now evaluate the second operand (let's call the result
y
) - Try to bind the
&
or|
operator based on the runtime type ofx
andy
(fail if unable) - Apply the selected operator
This is the behavior that lets through certain "illegal" combinations of operands: the ?:
operator successfully treats the first operand as a non-nullable boolean, the &
or |
operator successfully treats it as a nullable boolean, and the two never coordinate to check that they agree.
So it's not that dynamic && and || work on nullables. It's just that they happen to be implemented in a way that is a little bit too lenient, compared with the static case. This should probably be considered a bug, but we will never fix it, since that would be a breaking change. Also it would hardly help anyone to tighten the behavior.
Hopefully this explains what happens and why! This is an intriguing area, and I often find myself baffled by the consequences of the decisions we made when we implemented dynamic. This question was delicious - thanks for bringing it up!
Mads
Is this correct behavior?
Yes, I'm pretty sure it is.
How can you infer that from the spec?
Section 7.12 of C# Specification Version 5.0, has information regarding the conditional operators &&
and ||
and how dynamic binding relates to them. The relevant section:
If an operand of a conditional logical operator has the compile-time type dynamic, then the expression is dynamically bound (§7.2.2). In this case the compile-time type of the expression is dynamic, and the resolution described below will take place at run-time using the run-time type of those operands that have the compile-time type dynamic.
This is the key point that answers your question, I think. What is the resolution that happens at run-time? Section 7.12.2, User-Defined conditional logical operators explains:
- The operation x && y is evaluated as T.false(x) ? x : T.&(x, y), where T.false(x) is an invocation of the operator false declared in T, and T.&(x, y) is an invocation of the selected operator &
- The operation x || y is evaluated as T.true(x) ? x : T.|(x, y), where T.true(x) is an invocation of the operator true declared in T, and T.|(x, y) is an invocation of the selected operator |.
In both cases, the first operand x will be converted to a bool using the false
or true
operators. Then the appropriate logical operator is called. With this in mind, we have enough information to answer the rest of your questions.
But the evaluation for xx of A || B lead to no binding-time exception, and only the property A was read, not B. Why does this happen?
For the ||
operator, we know it follows true(A) ? A : |(A, B)
. We short circuit, so we won't get a binding time exception. Even if A
was false
, we would still not get a runtime binding exception, because of the specified resolution steps. If A
is false
, we then do the |
operator, which can successfully handle null values, per Section 7.11.4.
Evaluating A && B (for yy) also leads to no binding-time error. And here both properties are retrieved, of course. Why is this allowed by the run-time binder? If the returned object from B is changed to a "bad" object (like a string), a binding exception does occur.
For similar reasons, this one also works. &&
is evaluated as false(x) ? x : &(x, y)
. A
can be successfully converted to a bool
, so there is no issue there. Because B
is null, the &
operator is lifted (Section 7.3.7) from the one that takes a bool
to one that takes the bool?
parameters, and thus there is no runtime exception.
For both conditional operators, if B
is anything other than a bool (or a null dynamic), runtime binding fails because it can't find an overload that takes a bool and a non-bool as parameters. However, this only happens if A
fails to satisfy the first conditional for the operator (true
for ||
, false
for &&
). The reason this happens is because dynamic binding is quite lazy. It won't try to bind the logical operator unless A
is false and it has to go down that path to evaluate the logical operator. Once A
fails to satisfy the first condition for the operator, it will fail with the binding exception.
If you try B as first operand, both B || A and B && A give runtime binder exception.
Hopefully, by now, you already know why this happens (or I did a bad job explaining). The first step in resolving this conditional operator is to take the first operand, B
, and use one of the bool conversion operators (false(B)
or true(B)
) before handling the logical operation. Of course, B
, being null
cannot be converted to either true
or false
, and so the runtime binding exception happens.
The Nullable type does not define Conditional logical operators || and &&. I suggest you following code:
bool a = true;
bool? b = null;
bool? xxxxOR = (b.HasValue == true) ? (b.Value || a) : a;
bool? xxxxAND = (b.HasValue == true) ? (b.Value && a) : false;
'Programing' 카테고리의 다른 글
x-tmpl은 무엇입니까? (0) | 2020.09.19 |
---|---|
Scikit 학습의 RandomForestClassifier 대 ExtraTreesClassifier (0) | 2020.09.19 |
줄리아 REPL에서 사용자 정의 함수 설명 ( "독 스트링")을 사용하는 방법은 무엇입니까? (0) | 2020.09.19 |
Visual Studio Code : 파일 변경 자동 새로 고침 (0) | 2020.09.19 |
MSDN을 항상 영어로 설정하는 방법 (0) | 2020.09.19 |