Check null in C# 6 with default value - c#

I am using C# 6 and I have the following:
public class Information {
public String[] Keywords { get; set; }
}
Information information = new Information {
Keywords = new String[] { "A", "B" };
}
String keywords = String.Join(",", information?.Keywords ?? String.Empty);
I am checking if information is null (in my real code it can be). If it is than join a String.Empty since String.Join gives an error when trying to join null. If it is not null then just join information.Keywords.
However, I get this error:
Operator '??' cannot be applied to operands of type 'string[]' and 'string'
I was looking on a few blogs and supposedly this would work.
Am I missing something?
What is the best alternative to do this check and join the string in one line?

As the types must match on either side of the ?? (null-coalescing) operator you should pass a string array, in this case you could pass an empty string array.
String keywords = String.Join(",", information?.Keywords ?? new string[0]);

The best alternative would be to check for null before joining the strings:
var keywords = information?.Keywords == null ? "" : string.Join(",", information.Keywords);

Related

C# Linq Filter IEnumerable dynamic property and value

public class Sample
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime EffectiveDate { get; set; }
}
IEnumerable<Sample> sampleList;
//Populate the list
Now I want to filter the list some times by "Id" property, sometimes "Description" property. Just want to pass the property name (filterColumn) and property value (filterValue) both as strings.
I tried the following:
IEnumerable<Sample> result = sampleList.Where(x => x.GetType().GetProperty(filterColumn).Name == filterValue);
and
string whereQuery = string.Format(" {0} = \"{1}\"", filterColumn, filterValue);
IEnumerable<Sample> result = sampleList.AsQueryable().Where(whereQuery);
The second option works, if i pass the filterColumn as "Description", but throws incompatible '=' operator between string and int error when "Id" is passed as filterColumn and some filterValue like "1".
Appreciate any help. Thanks
Your first approach can work. Expanding on Jon Skeet's comment, here's the adjusted statement.
IEnumerable<Sample> result = sampleList.Where(
x => x.GetType().GetProperty(filterColumn).GetValue(x, null).Equals(filterValue)
);
To put a little context around this, you would have to allow for the differing data types. You can do this at least two ways: use a generic method or use the object data type. For illustrative purposes, I'll use the object approach.
public IEnumerable<Sample> GetFiltered(
IEnumerable<Sample> samples, string filtercolumn, object filtervalue
{
return samples.Where(
x => x.GetType().GetProperty(filtercolumn).GetValue(x, null).Equals(filtervalue)
);
}
IEnumerable<Sample> sampleList;
var byId = GetFiltered(sampleList, "Id", 100);
var byDescription = GetFiltered(sampleList, "Description", "Some Value");
This example is not really safe as there is no type checking to ensure that the property value will be of the same data type that you are passing in. For example, there is nothing stopping you from passing "Description" and 100 as parameters. You can't do a meaningful comparison between an integer and a string so you will always come up with an empty result. The Equals method does not throw an exception, it just sees that the two objects are different. As Jon pointed out, you'll always want to use Equals in this case rather than the "==" operator. The Equals method is intended to compare content while "==" compares references. Example:
Console.WriteLine(12 == 12);
// True
object a = 12;
object b = 12;
Console.WriteLine(a == b);
// False - because, due to boxing, a and b are separate objects
// that happen to contain the same value. (Check out "boxing"
// if this doesn't make sense.)
Console.WriteLine(a.Equals(b));
// True - because the Equals method compares content (value)
Also, note that strings have some special behaviors when using the "==" operator. The important thing to remember is that there is a difference between references (containers) and content. You want to compare content and that means Equals. (I have noticed that the Immediate window in Visual Studio is inconsistent in its results regarding strings when using "==". I suspect this is because string references can be, but are not always, optimized in that window.)
You state that your second approach works. I have not seen this type of filter string in a standard IEnumerable.Where method. So I am guessing that you are using some extensions. Your example does not work as shown. The DataTable class uses filter strings that match your usage. In general, a filter string has to be constructed in different ways based on data type. For example, a string requires the quotes (which you have) but an integer value does not use quotes.
Another option that you have is to set up a dictionary with the required operations.
public IEnumerable<Sample> GetFiltered(
IEnumerable<Sample> samples, string property, string value)
{
var map = new Dictionary<string, Func<string, Func<Sample, bool>>>()
{
{ "Description", v => s => s.Description == v },
{ "Id", v => s => s.Id == int.Parse(v) },
};
return samples.Where(map[property](value));
}
The advantage here is that you can perform a more complex comparison, such as adding custom filters by ranges of values, or those containing more than one property.

Shorthand to cast a string value

I'm not talking about javascript, but in javascript, I can declare a string like this:
var identity = {
getUserId: function () {
return 'userid';
}
};
var userid = identity.getUserId() || '';
That means: if identity.getUserId() is null or undefined, the value '' will be casted to userid automatically.
Now, in C#:
public static void AddOnlineUser(this IIdentity identity)
{
string userid = identity.GetUserId();
// long way to check userid is null or not:
if (string.IsNullOrEmpty(userid))
{
userid = "";
}
// invalid C# syntax:
// Operator || cannot be applied to operands of type 'string' and 'string'
// string userid = idenity.GetUserId() || "";
// Only assignment, call, increment, decrement, and new object expressions can be used as a statement
// string.IsNullOrEmpty(userid) ? "" : userid;
}
I don't mean: want to create a C# variable same as javascript syntax. But in this case, is there a way to cast value "" to userid if it's null or empty in 1 line?
The Null Coalescing Operator ??
C# has it's own null-coalescing operator ?? to do this to handle null values :
// This will use the available GetUserId() value if available, otherwise the empty string
var userid = identity.GetUserId() ?? "";
Keep in mind this operator will only work as expected if the first value in your statement is null, otherwise it will use that value. If there is a chance that this isn't the case (and you might encounter a non-null invalid value), then you should consider using a ternary operator instead.
The Ternary Operator ?:
Otherwise, you could use a ternary operator ?: (i.e. an inline if-statement) to perform this check as well. This is similar to the example you provided, however it's worth noting that you need to actually set userid to the result :
// This will set it to empty string if null or empty, otherwise it will use the returned id
userid = String.IsNullOrEmpty(userid) ? "" : userid;

C# ?? null coalescing operator LINQ

I am trying to prevent having NULL values when I parse an XML file to a custom object using LINQ.
I found a great solution for this on Scott Gu's blog, but for some reason it does not work for integers with me. I think I have used the same syntax but it seems I am missing something. Oh and for some reason it works when the node is not empty.
Below is an extract of my code.
List<GrantAgresso> lsResult = (from g in xml.Element("root").Elements("Elementname")
select new GrantAgresso()
{
Year = (int?)g.Element("yearnode") ?? 0,
Subdomain = (string)g.Element("domainnode") ?? ""
}).ToList();
The errormessage is:
Input string was not in a correct format.
If anyone has a clue as to what I'm doing wrong, please help :)
Edit: piece of XML (strange names but it's not by choice)
<Agresso>
<AgressoQE>
<r3dim_value>2012</r3dim_value>
<r0r0r0dim_value>L5</r0r0r0dim_value>
<r7_x0023_province_x0023_69_x0023_V005>0</r7_x0023_province_x0023_69_x0023_V005>
<r7_x0023_postal_code_x0023_68_x0023_V004 />
<r7_x0023_country_x0023_67_x0023_V003>1004</r7_x0023_country_x0023_67_x0023_V003>
<r7_x0023_communitydistrict_x0023_70_x0023_V006>0</r7_x0023_communitydistrict_x0023_70_x0023_V006>
</AgressoQE>
</Agresso>
It is this expression which is throwing:
(int?)g.Element("yearnode")
That's because if the actual value of the element's text node is String.Empty and not null, since the empty string is not a valid format for Int32.Parse, the attempted cast fails.
If the element is missing completely from your XML, this works as you expect, but if there is an empty tag <yearnode/> or <yearnode></yearnode>, you'll get the exception.
The following extension method will return 0 both if the element is not present, the element is empty or it contains a string that cannot be parsed to integer:
public static int ToInt(this XElement x, string name)
{
int value;
XElement e = x.Element(name);
if (e == null)
return 0;
else if (int.TryParse(e.Value, out value))
return value;
else return 0;
}
You could use it like this:
...
Year = g.ToInt("r3dim_value"),
...
Or if you're ready to consider the cost of reflection and to accept the default value of any value type, you may use this extension method:
public static T Cast<T>(this XElement x, string name) where T : struct
{
XElement e = x.Element(name);
if (e == null)
return default(T);
else
{
Type t = typeof(T);
MethodInfo mi = t.GetMethod("TryParse",
BindingFlags.Public | BindingFlags.Static,
Type.DefaultBinder,
new Type[] { typeof(string),
t.MakeByRefType() },
null);
var paramList = new object[] { e.Value, null };
mi.Invoke(null, paramList);
return (T)paramList[1]; //returns default(T), if couldn't parse
}
}
and use it:
...
Year = g.Cast<int>("r3dim_value"),
...
You can add Where operator
.....
.Where(a => ! string.IsNullOrEmpty(a)).ToList();
If year is null or empty string that you will get an "Input string was not in a correct format" exception. You may write an extension method to read values. I haven't tested code below, but it may give you some hints.
public static ReadAs<T>(this XElement el, T defaultValue) {
var v = (string)el; // cast string to see if it is empty
if (string.IsNullOrEmpty(v)) // test
return defaultValue;
return (T)el; // recast to our type.
}
The message Input string was not in a correct format looks like the one thrown by int.parse () so it could be that you have a yearnode with a value (not null) but which cannot be successfully parsed to an integer value.
Something like this may fix it:
List<GrantAgresso> lsResult = (from g in xml.Element("root").Elements("Elementname")
let yearNode = g.Element("yearnode")
select new GrantAgresso
{
Year = string.IsNullOrWhiteSpace(yearNode.Value) ? 0 : int.Parse(yearNode.Value),
Subdomain = g.Element("domainnode").Value
}).ToList();
A couple of things to note:
select new GrantAgresso - you don't need parenthesis for a default constructor with object initializers.
string.IsNullOrWhiteSpace - was introduced in .net 4.0, use string.IsNullOrEmpty if you're on 3.5 or earlier
g.Element("domainnode").Value - will always return a string
if you want a null for Year instead of 0, use (int?)null instead of 0
Your problem is that the cast from XElement to int? uses int.Parse method on the string value of the XElement, which in your case is String.Empty. The following results in the same error:
XElement x = new XElement("yearnode", String.Empty);
int? a = (int?)x; // throws FormatException
You can avoid this by first casting the XElement to string and checking if it is null or empty and only if not doing the cast to int?. To do this, replace
Year = (int?)g.Element("yearnode") ?? 0,
with
Year = !string.IsNullOrEmpty((string)g.Element("yearnode")) ? (int?)g.Element("yearnode") : 0,
Its not pretty and will still throw if the string is otherwise not legal, but it works if you can assume that the element is always an integer or null/empty.

List.FindAll() shows error for null column values

objLst = objLst.FindAll(c => c.Emp_cod.Equals(string.Empty)
|| c.Emp_cod.Equals(null))
I am having a List of All Employees and New Employees who have been offered has emp_cod value null.
Now when i am trying to find New Employees using the above code it gives object reference error.
Emp_cod column is string defined as below when imported from SQL to DBML:
[Column(Storage = "_Emp_cod", DbType = "VarChar(10)")]
public string Emp_cod { get; set; }
You can try:
objLst = objLst.Where(c => String.IsNullOrEmpty(c.Emp_cod));
The reason you are getting the error is because you are trying to call instance method Equals on null object. You need to check for null first and then check for string empty.
objLst = objLst.FindAll(c => c.Emp_cod != null && c.Emp_cod.Equals(string.Empty));
Or better if you may use string.IsNullOrEmpty like Adrian's answer.
You may also try string.IsNullOrWhiteSpace if you want to check against, null, empty and white spaces, but only if you are using .Net 4.0 or higher
objLst = objLst.FindAll(c => string.IsNullOrWhiteSpace(c.Emp_code))

Linq / XML - How do you handle non existing nodes?

I am trying to figure out how to handle nodes that do not exist for all of my "card" elements. I have the following linq query:
FinalDeck = (from deck in xmlDoc.Root.Element("Cards")
.Elements("Card")
select new CardDeck
{
Name = deck.Attribute("name").Value,
Image = deck.Element("Image").Attribute("path").Value,
Usage = (int)deck.Element("Usage"),
Type = deck.Element("Type").Value,
Strength = (int)deck.Element("Ability") ?? 0
}).ToList();
with the Strength item, I had read another posting that the ?? handles the null. I am getting the following error though:
Operator '??' cannot be applied to operands of type 'int' and 'int'
How do I handle this issue?
Thanks!
Rather than use the Value property, cast to string... and for the int, cast to int? instead. The user defined conversions to nullable types will return null if the source XAttribute/XElement is null:
FinalDeck = (from deck in xmlDoc.Root.Element("Cards")
.Elements("Card")
select new CardDeck
{
Name = (string) deck.Attribute("name"),
Image = (string) deck.Element("Image").Attribute("path"),
Usage = (int?) deck.Element("Usage"),
Type = (string) deck.Element("Type"),
Strength = (int?) deck.Element("Ability") ?? 0
}).ToList();
Note that this won't help for the case where the Image element is missing, as then it'll try to dereference a null element to find the path attribute. Let me know if you want a workaround for that, but it'll be a bit of a pain, relatively speaking.
EDIT: You can always create an extension method for this yourself:
public static XAttribute NullSafeAttribute(this XElement element, XName name)
{
return element == null ? null : element.Attribute(name);
}
Then call it like this:
Image = (string) deck.Element("Image").NullSafeAttribute("path"),

Categories

Resources