Is converting a NameValueCollection to a querystring using a c# lambda efficient? - c#

In researching how to convert a NameValueCollection to a querystring, I have come across different methods. I am curious if the shorter lambda syntax is as efficient as it could be.
How to convert NameValueCollection to a (Query) String using a iterating function.
public static String ConstructQueryString(NameValueCollection parameters)
{
List<String> items = new List<String>();
foreach (String name in parameters)
items.Add(String.Concat(name, "=", System.Web.HttpUtility.UrlEncode(parameters[name])));
return String.Join("&", items.ToArray());
}
Join a NameValueCollection into a querystring in C# uses a lambda expression, which looks nice but I'm not sure if it is efficient code.
private static string JoinNvcToQs(NameValueCollection qs)
{
return string.Join("&", Array.ConvertAll(qs.AllKeys, key => string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(qs[key]))));
}

I would do it like this:
public static string ConstructQueryString(NameValueCollection parameters)
{
var sb = new StringBuilder();
foreach (String name in parameters)
sb.Append(String.Concat(name, "=", System.Web.HttpUtility.UrlEncode(parameters[name]), "&"));
if (sb.Length > 0)
return sb.ToString(0, sb.Length - 1);
return String.Empty;
}
This way you create less objects (that have to be cleaned up by the garbage collector)

First of all, the best thing you can do is test and see if the performance is acceptable for your application, we can tell you generalities about performance but in the end it comes down to your needs and only you know the answers to that.
As to the question at hand, any time you use a delegate (which is what a lambda creates) rather than executing the code directly you'll take a performance hit. In most cases the hit is acceptable but if this code needs the absolute best possible performance (say it's in an inner loop) then you need to go with your first method.
That said, if you're creating a querystring, presumably you're about to hit the database which will likely take considerably longer than either method of creating the querystring in the first place.

NameValueCollection's ToString method will build the query string for you. I haven't done any benchmarking, but I'd imagine the implementation would be more efficient than something using lambdas or foreach.
(The ToString solution doesn't seem to be well-documented; I only found it because this answer used it in a code sample.)

Related

Convert string join utility function into LINQ equivalent

So I have a helper function that takes a particular list, performs a loop and returns a string joined with comma without duplication.
public static string GetJoinedEquipString(List<MeasuredData> dataList)
{
HashSet<string> equipSet = new HashSet<string>();
foreach (MeasuredData data in dataList)
{
equipSet.Add(data.GetEquipNumString());
}
return String.Join(",", equipNumSet.ToArray());
}
Which works fine.. but is there a way to rewrite this hideous looking function using LINQ?
Try:
string.Join(",", dataList.Select(d => d.GetEquipNumString()).Distinct().ToArray());
Instead of using a HashSet to make the list unique, look at Distinct, documented here. This will use the default equality comparer for string (as does your implementation), but there is also an overload that accepts an IEqualityComparer if you are ever using more complex objects.
If you're using .NET 4.0 or newer, you can leave off the call to ToArray since an overload of Join was added that has an IEnumerable<string> as a parameter (as opposed to just string[]).

How to handle null strings?

Say I have the following code:
Request.QueryString["ids"].Split('|');
If ids is not present in the query string this will throw an exception. Is there a generally accepted way to handle this type of situation. I think all of the following options would keep this from thorwing an error, but I'm wondering if one (or some different method entirely) is generally accepted as better.
string[] ids = (Request.QueryString["ids"] ?? "").Split('|');
or
string[] ids;
if(!String.IsNullOrEmpty(Request.QueryString["ids"]))
{
ids = Request.QueryString["ids"].Split('|')
}
or
?
I think all of these will work, but they look sort of ugly. Is there a better* way?
*better = easier to read, faster, more efficient or all of the above.
I like using an extension method for this:
public static string EmptyIfNull(this string self)
{
return self ?? "";
}
Usage:
string[] ids = Request.QueryString["ids"].EmptyIfNull().Split('|');
Personally I'd use
string idStr = Request.QueryString["ids"];
ids = idStr == null ? new string[0] : idStr.Split("|");
string[] ids = (Request.QueryString["ids"] as string).Split('|');
This will fail in the same manner as Request.QueryString["ids"]
string[] ids;
if(!String.IsNullOrEmpty(Request.QueryString["ids"]))
{
ids = Request.QueryString["ids"].Split('|')
}
Heavier and may call the data retrieval logic twice (and you might have side-effects done twice by error) => use a temporary to store the data but heavier.
string[] ids = (Request.QueryString["ids"] ?? "").Split('|');
Definetely the easiest, cleanest and more efficient way as the compiler will generate a temporary itself.
If you encounter this kind of processing a lot of time you can build your own plumbing library with a bunch of methods with fluent names and behaviors.

Is there a way to make it appear like a foreach look returns a value?

This is something I have always wondered about, and looked up a few times but have never figured out.
So basically what I want to do is get something to this effect:
List<string> strings = new List<string>(){"a","b","c"};
string aString = foreach(string s in strings){ if (s == "c") return s;}
so then after that, aString has the value "c".
I have tried using lambda expressions, maybe I just cant get them to work right, or maybe there is just no way to do this.
And obviously I want to do something a bit more complicated than in my example above, but it will work the same way.
Possible? not possible?
You should use the FirstOrDefault Extension method.
List<string> strings = new List<string>(){"a","b","c"};
return strings.FirstOrDefault(s=>String.Equals(s, "a"));
You can use LINQ (to objects):
List<string> strings = new List<string>(){"a","b","c"};
string aString = strings.Where(x => x.Equals("a")).FirstOrDefault();
The Where() methods iterates through the enumerable, and "returns" each element that satisfies the lambda. To get the first such element, you can chain on the FirstOrDefault() method (which will return default(string) if no elements meet the criteria.)
As #MichaelGraczyk points out, you can actually reduce the call to only FirstOrDefault(), since it has an overload that accepts a predicate:
string aString = strings.FirstOrDefault(x => x.Equals("a"));
There are a number of other useful methods available, which you can read about here.
It's kind of pointless in this particular example because you already know the string you want but in any case I think this is what you're trying to do...
List<string> strings = new List<string>(){"a","b","c"};
string aString = strings.Find((string s) => s == "a");

String concatenation optimisation

We're currently using LINQ to generate SQL queries, with a bit of magic inside to handle case-specific queries.
Up until now, it's worked fine; very fast, hardly any issues. We've recently run into efficiency issues when querying a large amount of data from the database.
We construct the query as such:
var someIntList = new List<int> { 1,2,3,4,5 };
var query = dtx.Query.Containers.Where(c => c.ContainerID.IsIn(someIntList));
or
var someStringList = new List<int> {"a", "b", "c" };
query = dtx.Query.Containers.Where(c => c.BuildingName.IsIn(someStringList));
Which would generate (along with a bunch of other stuff which isn't related to this):
SELECT * FROM Container WHERE ContainerID IN (1,2,3,4,5)
and
SELECT * FROM Container WHERE BuildingName IN ('a','b','c')
Now in this particular situation, we need to return 50,000 rows .. which is generated through 5 seperate queries, splitting up the load.
The DB returns fairly quickly (within seconds), however generating the query takes a long time.
Here's the very last function which is called to generate this particular query:
private static string GetSafeValueForItem(object item)
{
if (item == null)
return "NULL";
if (item is bool)
return ((bool)item ? "1" : "0");
if (item is string)
return string.Format("'{0}'", item.ToString().Replace("'", "''"));
if (item is IEnumerable)
return ListToDBList((IEnumerable)item);
if (item is DateTime)
return string.Format("'{0}'", ((DateTime)item).ToString("yyyy-MM-dd HH:mm:ss"));
return item.ToString();
}
private static string ListToDBList(IEnumerable list)
{
var str = list.Cast<object>().Aggregate("(", (current, item) => current + string.Format("{0},", GetSafeValueForItem(item)));
str = str.Trim(',');
str += ")";
return str;
}
Are there any obvious improvements which can be made to speed up the string concatenation in this case? Refactoring the code and using a different implementation (such as avoiding the query generating and hitting the database directly) is not preferred, but if it offered a big performance boost would be great to hear.
Your Aggregate code is basically string concatenation in a loop. Don't do that.
Options:
Use StringBuilder
Use string.Join
Here's an example using String.Join that outputs the same as your ListToDBList:
String.Format("({0})", String.Join(",", list.Cast<object>().Select(item=>GetSafeValueForItem(item)).ToArray()));
See here for an explanation why concatenating in a loop using + (which is what your call to Aggregate was doing) is slow: http://www.yoda.arachsys.com/csharp/stringbuilder.html
I haven't made test cases and profiled your code, so I don't know how much improvement you can expect.
Use a StringBuilder instead of String.Format and the += operator. The += operator is known to be slow. I suspect String.Format is going to be somewhat slow, too.
You could also try string.Join instead of manually joining the array. It works on IEnumerable in newer versions of the .NET framework (4.0?).
Not sure why you're doing list.Cast when a plain IEnumerable will be of objects anyway. But your whole ListToDBList can be replaced by
string.Format("({0})", string.Join(",",list.ToArray()));
Not sure how much quicker it would be, but it's clearer to my mind.

What is the most readable use of String.Format for long strings with many parameters?

For instance:
String login = String.Format("computer={0}&ver={1}.{2}.{3}&from={4}&realcomputername={5}&type={6}&Channels={7}&Hotkeys={8}&ID={9}\r\n",
serviceConfig.Computer,
serviceConfig.Version.Major,
serviceConfig.Version.Minor,
serviceConfig.Version.Build,
userName,
Environment.MachineName,
type,
serviceConfig.ChannelsString,
serviceConfig.HotKeysString,
serviceConfig.AlarmGroupName);
This does not make for very readable code, and as more and more parameters get added, it looks uglier and is more confusing to find which parameter goes in which slot.
I know this is a noob question, and I think I'm only asking for how to format the text to be more readable, but if there's a better way to do this, I'd like to know that too.
You could look at the StringBuilder class and split the assembly of the string over several lines.
The AppendFormat method (thanks Joel) is what you want in this case.
String login = String.Format(
"computer={0}"+
"&ver={1}.{2}.{3}"+
"&from={4}"+
"&realcomputername={5}"+
"&type={6}"+
"&Channels={7}"+
"&Hotkeys={8}"+
"&ID={9}\r\n",
serviceConfig.Computer,
serviceConfig.Version.Major,
serviceConfig.Version.Minor,
serviceConfig.Version.Build,
userName,
Environment.MachineName,
type,
serviceConfig.ChannelsString,
serviceConfig.HotKeysString,
serviceConfig.AlarmGroupName);
Assuming you can use LINQ, you can shove your arguments into a Dictionary<string, string>, then join the arguments together:
Dictionary<string, string> args = new Dictionary<string, string>
{
{"computer", serviceConfig.Computer},
{"ver", string.Format("{0}.{1}.{2}",
serviceConfig.Version.Major,
serviceConfig.Version.Minor,
serviceConfig.Version.Build)},
{"from", userName},
{"realcomputername", Environment.MachineName},
{"type", type},
{"Channels", serviceConfig.ChannelsString},
{"Hotkeys", serviceConfig.HotKeysString},
{"ID", serviceConfig.AlarmGroupName},
};
string login = string.Join("&", args.Select(arg =>
string.Format("{0}={1}", arg.Key, arg.Value)).ToArray());
This will be some miniscule amount slower and more memory-intensive than a simple string.Format, but it looks like you're about to make an HTTP request, so I can almost guarantee that it won't be the bottleneck.
That final line can also be pulled out into an extension method that you can use anytime you want to build a query string like this.
Also, it's important to note that since Dictionary does not preserve insertion order, you aren't guaranteed that the parameters in the query string will be in that exact order. That shouldn't matter, but in case it does you can replace the Dictionary with a List<KeyValuePair<string, string>> (OrderedDictionary should also work).

Categories