I should be able to do something like:
var table = new HtmlTable {
Rows = { // <-- error is here
from XmlNode p in node.SelectNodes("./property")
select new HtmlTableRow{
Cells = {
new HtmlTableCell{ InnerText = Server.HtmlEncode(p.Attributes["description"].Value) },
new HtmlTableCell{ InnerText = Server.HtmlEncode(p.InnerText) }
}
}
}
};
But I can't, because the LINQ code isn't "expanded" so the compiler complains that while it expects an HtmlTableRow element between the braces assigned to Rows, you're giving it a collection. But, you can't assign the collection to Rows directly because it's a read-only property.
Bad class design (Rows being read-only... same as Cells fwiw) or bad language design in that there's no way to "expand" the results of the LINQ query to do what seems natural in that spot?
Instead, I have to do:
var rows =
from XmlNode p in node.SelectNodes("./property")
select new HtmlTableRow{
Cells = {
new HtmlTableCell{ InnerText = Server.HtmlEncode(p.Attributes["description"].Value) },
new HtmlTableCell{ InnerText = Server.HtmlEncode(p.InnerText) }
}
};
var table = new HtmlTable();
foreach (var r in rows)
table.Rows.Add(r);
which is redundant.
Say you want to search a string with a regular expression and return all the matches concatenated together, using LINQ:
var str = "some string";
var matches = Regex.Matches(str, @"REGEX");
Three ways to concatenate:
Using String.Join (simplest):
var foo = String.Join("", (from Match match in result select match.Value).ToArray());
Using an accumulator:
var foo = (from Match match in matches select match.Value)
.Aggregate(new StringBuilder(), (sb, s) => sb.Append(s)).ToString();
Using a loop:
var sb = new StringBuilder();
foreach (var s in (from Match match in result select match.Value)){
sb.Append(s);
}
var foo = sb.ToString();
First seems most concise, and it's easier to specify a join character with. Apparently there's no way to massage an IEnumerable(T) into a String.Join, so unfortunately you need to pass it an array, which makes the IEnumerable fill out all its values into an array (so it can't be lazy), and then String.Join makes another pass over that array and copies the values into a string. So it uses double the space and double the time.
The second is more obscure, but only makes one pass over the result, though it's harder to specify a join character if desired.
The third is presumably most efficient, but it's more verbose.
In conclusion, there should be a String.Join(IEnumerable<T>).
Edit: Though it's impossible to define a "static" extension method (like, another variation of String.Join), you can define a Join method on IEnumerable like so:
static class Extentions{
public static string Join(this IEnumerable source, string separator){
var sb = new StringBuilder();
bool first = true;
foreach(var foo in source){
if(!first)
sb.Append(separator);
sb.Append(foo.ToString());
first = false;
}
return sb.ToString();
}
}
The first example above now becomes:
var foo = (from Match match in result select match.Value).Join("");
or even more concisely:
var foo = Regex.Matches(str, @"REGEX").Join("");
// (apparently the ToString on a Match object returns its Value)
Very cool.
Note: Works with custom ToString()s as expected:
class Custom{
public string Value { get; set; }
public override string ToString(){
return "VALUE: "+Value;
}
}
var list = new List<Custom> {
new Custom { Value = "one" },
new Custom { Value = "two" }
};
Console.WriteLine(list.Join(", "));
prints "VALUE: one, VALUE: two" as expected.
Final note: I would have used FirstOrDefault in my Join extension method instead of a first boolean, but the example of the regular expression object was chosen because it's not a generic, so I can't use IEnumerable<T>, only IEnumerable.
new⇒The Elegant Universe
Well I have finally found the crazyguy that preaches useless nonsencein A...
Joseph Baxter: Jan 7, 11:07pm