tags:

views:

113

answers:

5

Consider the following code that I'm using when displaying a Customer's mailing address inside a table in a view:

<%: Customer.MailingAddress == null ? "" : Customer.MailingAddress.City %>

I find myself using a fair amount of these ternary conditional statements and I am wondering if there is a way to refer back to the object being evaluated in the condition so that I can use it in the expression. Something like this, perhaps:

<%: Customer.MailingAddress == null ? "" : {0}.City %>

Does something like this exist? I know I can create a variable to hold the value but it would be nice to keep everything inside one tight little statement in the view pages.

Thanks!

+3  A: 

You can use ?? operator for comparisons with null.

Customer.MailingAddress == null ? "" : Customer.MailingAddress;

Above can be rewritten as following:

Customer.MailingAddress ?? "";

In your case I usually create extension method:

public static TValue GetSafe<T, TValue>(this T obj, Func<T, TValue> propertyExtractor)
where T : class
{
    if (obj != null)
    {
        return propertyExtractor(obj);
    }

    return null;
}

And usage would be like this:

Customer.GetSafe(c => c.MailingAddress).GetSafe(ma => ma.City) ?? string.Empty
Andrew Bezzub
The null coalescing operator wouldn't be practical here.
Daniel A. White
Updated answer.
Andrew Bezzub
All due respect for an innovative solution, but that's a lot of overhead (and arcane syntax) to avoid the repetition of a type name in a conditional operator...
djacobson
So two "get safes" and a null coalescing operator? He's trying to reduce code, specifically, he wants one tight little statement. I personally don't think this is an awesome fit.
McKay
@McKay: I think it is better than following: Customer != null ? (Customer.MailingAddress != null ? Customer.MailingAddress.City : string.Empty) : string.Empty
Andrew Bezzub
I think part of my concern with your GetSafe method is that it isn't really all that safe. The using code feels (to me at least) like GetSafe always returns an object, when in fact, it rarely does. The result of a GetSafe call always needs to be null checked or coalesced, or passed to another GetSafe call. A new developer using GetSafe, might see: "Customer.GetSafe(c => c.MailingAddress).GetSafe(ma => ma.City)" and think "Oh, I can dereference the result of a GetSafe call, and write "Customer.GetSafe(c => c.MailingAddress).GetSafe(ma => ma.City).Trim()" especially if he always fills city.
McKay
Agree, maybe naming should be better. Also it is possible to add defaultValue to be returned if obj is null.
Andrew Bezzub
+3  A: 

No, there is not a way to do precisely what you're asking without creating a variable or duplicating yourself, though you can do something like this:

(Customer.MailingAddress ?? new MailingAddress()).City ?? string.Empty

This presumes that a new MailingAddress will have it's city property / field null by default.

The last null coalescence can be removed if creating a new MailingAddress initializes the city field / property to empty string.

But this isn't actually shorter, and it's more hackish (in my opinion), and almost certainly less performant.

McKay
+1 Not because I'd use that, but I like how it uses the chaining :-)
pst
I don't think this will actually work to do what the OP wants. If Customer.MailingAddress is not null, then the result of the statement is the object Customer.MailingAddress, but the user wants the City property of that object not the object itself.
Adam Porad
@Adam Porad . That's not true. The result **of the first Paren Block** is always a dereferencable MailingAddress. We then get the City property / field of that MailingAddress (and null coalesce it if necessary).
McKay
Accepted this answer for bluntness and quirky example.
Terminal Frost
@McKay: You're right. I misread and I overlooked the parenthesis. I remember seeing the first parenthesis and thinking it might have been a typo or copy/paste error because I didn't see the matching end parentheis.
Adam Porad
+3  A: 

Why not create a property on the Customer object which contains that condition and just call it directly? i.e.

Customer.FormattedMailingAddress

I'd write the code but I'm on my mobile, you'd only need to put the same condition inside the get{} though.

Dave
I agree that this is a nice approach, I was just wondering if what I was asking was possible or not for examples where I wouldn't want to add an additional property.
Terminal Frost
For my project (.net4 and mvc2) we follow the pattern I've described, and do all of this (display string formatting) on the objects themselves. I like being able to just call a property from a view, and not worry about any unnecessary logic (at all) in the views themselves. It is a matter of taste though, and I hope my suggestion helps at least.
Dave
I should also say, that it means, when a property is used in multiple views, it retains the same 'formatting' (i.e. on a page developed in a year's time, it doesn't use different logic, producing a different string, for the same field, unless it's deliberate :) )
Dave
Just to add to the discussion: I had already created an extension method AddressString() which can be called on any address object in order to check for nulls and format it properly. I was simply curious about the C# conditional operator in general. Thanks again for the additional suggestion!
Terminal Frost
For what it's worth, I'm with Dave: Views shouldn't have to worry about whether the value they're receiving is valid or not; that's what the intervening logic between V and M is for (whether that's C, VM, or something else. :)) But there's nothing wrong with being curious about ?: itself!
djacobson
Ah, no bother, well I'm glad to give a slightly different slant on it, even if it's been considered :)
Dave
+1  A: 

I agree with @Dave, create an extension for your Customer class.

Something like this:

public static class CustomerExtension
{
    public static string DisplayCity(this Customer customer)
    {
        return customer.MailingAddress == null ? String.Empty : customer.MailingAddress.City
    }
}

Then you can call your method like this:

myCustomer.DisplayCity();

(note: extensions can not be created as a property, so this would have to be a method. see http://stackoverflow.com/questions/619033/c-extension-properties for more details)

Remus
+1  A: 

You could create an extension method to get the value or return an empty string:

    public static string GetValue<T>(this T source, Func<T, string> getter)
    {
        if (source != null)
            return getter(source);

        return "";
    }

then call it:

<%: Customer.MailingAddress.GetValue(x=>x.City) %>

This would work for any object.

Kelly Ethridge