C# 4.0 Implicitly Typed Dynamic Objects
Data File: (Data.txt) lines represent width height
5
6 9
7 2
4 4
C# Code:
var list = new List<dynamic>();
using (var sr = new StreamReader("Data.txt", Encoding.UTF8))
{
list = sr.ReadToEnd().Split('\n').Select(r =>
{
var split = r.Split(' ');
var len = split.Length;
return new {
w = len > 0 ? int.Parse(split[0].Trim()) : 0,
h = len > 1 ? int.Parse(split[1].Trim()) : 0
} as dynamic;
}).ToList();
}
int Area = list.Sum(r => r.h * r.w);
The example works as is. I had t开发者_JS百科o do a few undesired things to make it work.
First I had to declare the list to avoid the using scope - since I do not have a typed dimension object I made the type dynamic (var list = new List<dynamic>()
).
The undesirable part is casting the anonymous object to a dynamic (as dynamic
). Otherwise I get
Cannot implicitly convert type
System.Collections.Generic.List<AnonymousType#1>
toSystem.Collections.Generic.List<dynamic>
Why do I get this error? I know a dynamic can hold an anonymous type, so is this a problem with the ToList()
extension and dynamics?
I need to be able to access the anonymous list items outside of the using statement, as in the last line that calculates area.
Solution: I went with dtb's answer. It avoided the use of a using statement and dynamics all together. Thank you all for the input!
var list =
(from line in File.ReadLines("Data.txt")
let parts = line.Split(' ')
let width = int.Parse(parts[0])
let height = parts.Length > 1 ? int.Parse(parts[1]) : 0
select new { width, height }).ToList();
You can use File.ReadLines to avoid the StreamReader.
IEnumerable<dynamic> query =
from line in File.ReadLines("Data.txt")
let parts = line.Split(' ')
let width = int.Parse(parts[0])
let height = parts.Length > 1 ? int.Parse(parts[1]) : 0
select new { width, height } as dynamic;
List<dynamic> list = query.ToList();
int area = list.Sum(t => t.width * t.height);
However, as others have pointed out, using dynamic isn't really appropriate here. If you're using the query only within a method, an anonymous instance is good enough. If you want to use the result of the query outside the method, create a small struct or class or use Tuple<T1,T2>.
You really should do something like this:
private IEnumerable<Tuple<int, int>> ReadFile(string filePath,
Encoding encoding)
{
using (var sr = new StreamReader(filePath, encoding))
{
string line;
while ((line = sr.ReadLine()) != null)
{
var split = line.Split(' ');
var w = split.Length > 0 ? int.Parse(split[0]) : 0;
var h = split.Length > 1 ? int.Parse(split[1]) : 0;
yield return Tuple.Create(h, w);
}
}
}
Now you have a lazy and strongly typed sequence of values.
I'd get rid of the first line, then where the list is assigned declare it with var and move the last line inside the using statement:
int Area;
using (var sr = new StreamReader("Data.txt", Encoding.UTF8))
{
var list = sr.ReadToEnd().Split('\n').Select(r =>
{
var split = r.Split(' ');
var len = split.Length;
return new {
w = len > 0 ? int.Parse(split[0].Trim()) : 0,
h = len > 1 ? int.Parse(split[1].Trim()) : 0
};
}).ToList();
Area = list.Sum(r => r.h * r.w);
}
It has to do with co/contravariance.
You can not cast List<T1>
to List<T2>
. Try with
List<object> objectList = new List<string>{"hello"};
and you get the compile error "Cannot implicitly convert type 'System.Collections.Generic.List<string>
' to 'System.Collections.Generic.List<object>
'"
It is to prevent the error
List<string> stringList = new List<string>{"hello"};
List<object> objectList = stringList; // compile error
objectList.Add(new Car()); // this would add a Car object to the stringList if the above line was allowed
The reason why you cannot simply assign is the following:
In the first case, you’re getting a List<SomeAnonymousType>
. That type is completely unrelated to List<dynamic>
, even if an implicit conversion from the anonymous type to dynamic
exists. It’s a bit like implicitly converting a List<int>
to a List<float>
.
Dynamic is not your solution here. You simply have to calculate the area inside of the using statement.
Doing it outside of the using statement is a bad idea if you are trying to introduce deferred execution here; you will end up diposing of the StreamReader before you make the call to Sum.
In the end, your code should look like this:
int Area = 0;
using (var sr = new StreamReader("Data.txt", Encoding.UTF8))
{
Area = sr.ReadToEnd().Split('\n').Select(r =>
{
var split = r.Split(' ');
var len = split.Length;
return new {
w = len > 0 ? int.Parse(split[0].Trim()) : 0,
h = len > 1 ? int.Parse(split[1].Trim()) : 0
} as dynamic;
}).Sum(r => r.h * r.w);
}
Doing it this way is better, you are not materializing the list and then looping through it again, you are pulling the data and summing it as needed.
I mostly agree with other remarks, dynamic is probably not the go here, but for discussion, but as Albin points out, it is to do with generic parameters being invariant unless explicitly specified with the 'in' or 'out' modifiers.
You can make your code work by adding .Cast() before the call to .ToList()
精彩评论