First off, I know this question can be answered with a simple response that an empty string is not a null value. In addition, I only recently discovered the cast operators earlier in the year via another stackoverflow question and don't have a ton of experience with them. Even still, the reason why it's not entirely that simple is these cast operators when combined with the null coalescing operator, are billed as an elegant solution to dealing with error conditions like missing elements or attributes in LINQ expressions. I started using the approach described by ScottH in "Improving LINQ Code Smell..." and by ScottGu "Null coalescing operator (and using it with LINQ)" as a way to guard against invalid/missing data in a concise and semi-elegant fashion. From what I can gather that seems to be one of the motivations for putting all the cast overloads in the LINQ classes in the first place.
So, in my mind the use case of dealing with a missing value isn't all that different than dealing with an empty one and in the linked articles, this approach is billed as a good way to go about dealing with such situations.
Scenario:
int length = (int?)elem.Attribute("Length") ?? 0;
If the @Length attribute is missing, the cast results in a null value and the ?? operator returns 0;
If the @Length attribute exists but is empty the cast internally branches on int.tryparse and throws a format exception. Of course for my usage, I wish it wouldn't and would simply return null so I could continue using this approach in my already somewhat convoluted LINQ code.
Ultimately it's not so much that I can't come up with a solution but more so that I'm interested to hear if there's an obvious approach that I've missed or if it anyone has a good perspective on why the missing value scenario was addressed but empty value scenario was not.
Edit
It seems there's a few key points I should try and highlight:
The linked articles are from MS staff that I admire and look to for guidance. These articles seem to propose (or at least call attention to) an improved/alternate approach for dealing with optional values
In my case optional values sometimes come in the form of missing elements or attributes but also come in the form of empty elements or values
The described approach works as expected for missing values but fails for empty values
Code that follows the described approach appears to be guarding against the lack of optional values but in fact is fragile and breaks in a particular case. It's the appearance of protection when in fact you're still at risk that concerns me
This can be highlighted by stating that both linked examples fail with a runtime exception if the target element exists but is empty
Finally, it seems the key takeaway is the described approach works great when you have a contract in place (possibly via schema) which ensures the element or attribute will never be empty but in cases where that contract does not exist, this approach is invalid and an alternate is needed
I happen agree with the behaviour; if I don't have an "id" attribute / element, then I'm happy to count that as null, but if it exists but doesn't parse, that is different - and an empty string works for very few types. You would have to do it manually, or you could add an extension method on XAttribute
etc:
public static int? ParseInt32(this XAttribute attrib) {
if(attrib != null && string.IsNullOrEmpty(attrib.Value)) return null;
return (int?)attrib;
}
Note I'm using the cast internally as while it is simple for int
, a .Parse
would be a bad example for DateTime
etc, which uses a different format in xml, which the cast handles internally.
As far as coming up with a workaround, I had already built the extension method below. I hadn't thought of the approach described by Marc which fixes the problem with a nullable int and still uses the ?? operator for specifying the default. Very creative and allows the X ?? Y pattern to continue to be used.
I think in the end I probably prefer Marc's approach because it keeps a consistent pattern in my assignment statements and doesn't introduce the syntactical confusion that the TryParseInt call had as it appeared too much like you were parsing -1 or whatever default/fallback value was supplied.
elem.Attribute("ID").TryParseInt(-1)
/// <summary>
/// Parses an attribute to extract an In32 value and falls back to the defaultValue should parsing fail
/// </summary>
/// <param name="defaultValue">The value to use should Int32.TryParse fail</param>
/// <returns>The parsed or default integer value</returns>
public static int TryParseInt(this XAttribute attr, int defaultValue)
{
int result;
if (!Int32.TryParse((string)attr, out result))
{
// When parsing fails, use the fallback value
result = defaultValue;
}
return result;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With