问题
Suppose I want to check a bunch of objects to make sure none is null:
if (obj != null &&
obj.Parameters != null &&
obj.Parameters.UserSettings != null) {
// do something with obj.Parameters.UserSettings
}
It is an alluring prospect to write a helper function to accept a variable number of arguments and simplify this kind of check:
static bool NoNulls(params object[] objects) {
for (int i = 0; i < objects.Length; i++)
if (objects[i] == null) return false;
return true;
}
Then the above code could become:
if (NoNulls(obj, obj.Parameters, obj.Parameters.UserSettings)) {
// do something
}
Right? Wrong. If obj
is null, then I'll get a NullReferenceException
when I try to pass obj.Parameters
to NoNulls
.
So the above approach is clearly misguided. But the if
statement using the &&
operator works just fine since it is short-circuited. So: is there any way to make a method short-circuited, so that its arguments are not evaluated until explicitly referenced within the method?
回答1:
Well, this is ugly but...
static bool NoNulls(params Func<object>[] funcs) {
for (int i = 0; i < funcs.Length; i++)
if (funcs[i]() == null) return false;
return true;
}
Then call it with:
if (NoNulls(() => obj,
() => obj.Parameters,
() => obj.Parameters.UserSettings)) {
// do something
}
Basically you're providing delegates to evaluate the values lazily, rather than the values themselves (as evaluating those values is what causes an exception).
I'm not saying it's nice, but it's there as an option...
EDIT: This actually (and accidentally) gets to the heart of what Dan was after, I think. All a method's arguments are evaluated before the method itself is executed. Using delegates effectively lets you delay that evaluation until the method needs to call the delegate to retrieve the value.
回答2:
You could write a function that accepts an expression tree and transforms that tree into a form that will check for nulls, and return a Func<bool>
that could be safely evaluated to determine if there is a null.
I suspect that while the resulting code may be cool, it would be confusing, and much less performant than just writing a bunch of short-circuited a != null && a.b != null...
checks. In fact, it would likely be less performant than just checking all the values and catching the NullReferenceException
(not that I advocate for exception handling as a flow of control mechanism).
The signature for such a function would be something like:
public static Func<bool> NoNulls( Expression<Func<object>> expr )
and it's usage would look something like:
NoNulls( () => new { a = obj,
b = obj.Parameters,
c = obj.Parameters.UserSettings } )();
If I get some free time, I will write a function that does just such an expression tree transformation and update my post. However, I'm sure that Jon Skeet or Mark Gravell could write such a function with one eye closed and one hand behind their back.
I would also love to see C# implement the .?
operator that Eric alludes to. As a different Eric (Cartman) might say, that would "kick ass".
回答3:
You could use reflections if you don't mind losing static type safety. I do mind, so I just use your first short-circuiting construction. A feature like Eric mentioned would be welcome :)
I've thought about this problem a few times. Lisp has macros that solve the problem in the way you mentioned, since they allow you to customize your evaluation.
I have also tried using extension methods to solve this problem, but nothing there is less ugly than the original code.
Edit: (Replies don't let me insert code blocks, so editing my post)
Oops, didn't keep up on this. Sorry about that :)
You can use reflections to look up and evaluate a member or property via a string. A class one of my friends wrote took a syntax like:
new ReflectionHelper(obj)["Parameters"]["UserSettings"]
It worked via method chaining, returning a ReflectionHelper at each level. I know that NullReferenceException is a problem in that example. I just wanted to demonstrate how evaluation can be deferred to runtime.
An example slightly closer to being helpful:
public class Something
{
public static object ResultOrDefault(object baseObject, params string[] chainedFields)
{
// ...
}
}
Again, this syntax stinks. But this demonstrates using strings + reflections to defer evaluation to runtime.
来源:https://stackoverflow.com/questions/1917531/can-i-force-my-own-short-circuiting-in-a-method-call