Consider this code:
static void Main(string[] args)
{
var ints=new List<int> {10,11,22};
Something(ints);//Output:Count is:3
Something(new int[10]); //'System.Array' does not contain
// a definition for 'Count'
Console.ReadLine();
}
static void Something(ICollection collection)
{
dynamic dynamic = collection;
Console.WriteLine("Count is:{0}", dynamic.Count);
}
When pass a List all thing is ok. But when pass array and convert to dynamic i get this error:'System.Array' does not contain a definition for 'Count'.
I know what is my solution but i want to know why compiler has a this behaviors?
Something(new int[10]);
static void Something(ICollection collection)
{
//The dynamic keyword tells the compilier to look at the underlying information
//at runtime to determine the variable's type. In this case, the underlying
//information suggests that dynamic should be an array because the variable you
//passed in is an array. Then it'll try to call Array.Count.
dynamic dynamic = collection;
Console.WriteLine("Count is:{0}", dynamic.Count);
//If you check the type of variable, you'll see that it is an ICollection because
//that's what type this function expected. Then this code will try to call
//ICollection.Count
var variable = collection;
Console.WriteLine("Count is:{0}", variable.Count);
}
Now that we can understand why dynamic.Count is trying to call System.Array.Count. However, it's still unclear why Array.Count is not defined when Array implements System.Collections.ICollection which has a Count method. Array does in fact implement ICollection correctly, and it does have a Count method. However, consumers of Array.Count do not have permission to access the Count property without explicitly casting the Array to an ICollection. Array.Count is implemented with a pattern known as explicit interface implementation where Array.Count is explicitly implemented for ICollection. And you may only access the count method by casting your variable to an ICollection with this pattern. This is reflected in the docs for Array. Look for the "Explicit Interface Implementations" section.
var myArray = new int[10];
//Won't work because Array.Count is implemented with explicit interface implementation
//where the interface is ICollection
Console.Write(myArray.Count);
//Will work because we've casted the Array to an ICollection
Console.Write(((ICollection)myArray).Count);
Dynamic works internally using reflection. The array class does not have a property Count. It has a property Length which explicitly implements the ICollection property Count. This means that when you try to do the dynamic invocation, it fails, because it can not find a matching property.
My question for you would be why are you trying to use dynamic in this case -- you've already limited it to classes that support an interface, at that point you should be using the interface (which would work). At this point you're pretty much guaranteed to be able to get an enumerator and the count -- nothing else. If you need more, consider a better interface.
"I know what is my solution but i want to know why compiler has a this behaviors?"
For your question, as I know the compiler behavior is..
The compiler doesn't handle the dynamic type variable at compile-time, because the dynamic type is handled at the run-time. That's why that error appears.
If you want the compiler to handle this case, you can change your dynamic type into var type.
in a short words.. the dynamic type variable is not the compiler's responsibility.
It's because the name of the property is not Count, but rather System.Collections.ICollection.get_Count.
If you run
foreach (var item in typeof(int[]).GetMembers(BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Instance))
Console.WriteLine(item.Name);
you will get back
...
System.Collections.ICollection.get_Count
...
which is because the interface is implemented explicitly.
Related
This question already has answers here:
var keyword not always working?
(5 answers)
Closed 8 years ago.
Consider this working code:
RadComboBox rcb = new RadComboBox(); // Telerik.Web.UI.RadComboBox
rcb.Items.Add(new RadComboBoxItem("One", "1"));
rcb.Items.Add(new RadComboBoxItem("Two", "2"));
rcb.Items.Add(new RadComboBoxItem("Three", "3"));
// check all items
foreach (RadComboBoxItem i in rcb.Items)
{
i.Checked = true;
}
If I replace the foreach loop with var, it does not compile:
RadComboBox rcb = new RadComboBox(); // Telerik.Web.UI.RadComboBox
rcb.Items.Add(new RadComboBoxItem("One", "1"));
rcb.Items.Add(new RadComboBoxItem("Two", "2"));
rcb.Items.Add(new RadComboBoxItem("Three", "3"));
// check all items
foreach (var i in rcb.Items)
{
i.Checked = true;
}
The error is:
'object' does not contain a definition for 'Checked' and no extension method 'Checked' accepting a first argument of type 'object' could be
found (are you missing a using directive or an assembly reference?)
So, I wonder, what are the conditions when you cannot use var?
Edit: Just to clarify, I wasn't simply asking "why doesn't this work"? Here's an example that could make the problem more obvious:
List<Object> stuff = new List<object>();
stuff.Add("one");
stuff.Add("two");
stuff.Add("three");
foreach (string s in stuff)
{
if (s.Length > 3)
// do something
}
Now if you change string s in stuff to var s in stuff, it obviously isn't going to compile. You can't just willy-nilly go and replace any type with var and expect it to compile. The error in my thinking that led me to propose the question in the first place was that I assumed that since rcb.Items is of type RadComboBoxItemCollection, that the enumerable type would be RadComboBoxItem, and this is not the case.
Unfortunately my example tainted the question by leading some of the answers down the "why does substituting a type with var not work?" path. Paulo actually answered with what I was looking for (others partially did too), which is why I have awarded it the best answer.
You can use var any time you want to use implicit typing. That is, when the type can be derived from the declaration. It would seem in this case that the actual type of Items is a collection of objects. So that's all var knows in that declaration.
By explicitly declaring the type as RadComboBoxItem you are essentially doing something very similar to this (but with more compile-time checking):
foreach (var i in rcb.Items)
{
(i as RadComboBoxItem).Checked = true;
}
The object instances in Items can be implicitly converted to RadComboBoxItem, but var doesn't know to do that. You have to explicitly do that. (Personally I would consider that an odd design of RadComboBox, but it could be a holdover from earlier design decisions. Collections in .NET weren't always as strongly-typed as they are today. Things like ArrayList used to cause similar problems.)
The reason this is occurring in that for loop is because the compiler is unable to infer the type of the enumerable object. This occurs in older code that predates generics - with generics, the compiler is able to infer that the next item from the iterator will be of type T. However, without generics, we return a non-generic IEnumerator type, whose iterations only expose an object.
Remember that a for loop is basically syntactic sugar for invoking the Next() method on an enumerator, and then checking the Current property on aforementioned enumerator. Older types that pre-date generics did not have generics in their Enumerator, so they would always return an object (here is the actual method invoked).
The newer, strongly-typed enumerators however return a type of T when you call Current, so the type can be inferred.
Another example of this is the System.Data namespace when trying to iterate over a DataTable's rows.
In this case you should explicitly state the type of the variable.
I appreciate this might be long winded but I was trying to provide the reason why the type can't be inferred here and a little background because I was confused when I first had the issue trying to enumerate a DataRowCollection.
TLDR:
you're iterating over a non-generically typed enumerator when you do for. Because it is not generically typed, the enumerator returns instances of Object. Ergo, you get object instances back and can only use the methods available to Object until you cast the variable.
The RadComboBox Items property will return a RadComboBoxItemCollection, which implements both IEnumerable<RadComboBoxItem> and the non-generic IEnumerable.
Although I'm not familiar with Telerik's products, I suspect previous versions didn't implement IEnumerable<RadComboBoxItem>.
According to the C# language specification, it should work (C# >= 3.0 section 8.8.4), in a simpler English:
If the expression's type is an array, it's cast to IEnumerable and the element type is the array's element type
(C# >= 4.0) If the expression's type is dynamic, it's cast to IEnumerable and the element type is dynamic if var is used, object otherwise
If the expression's type quacks like IEnumerable and the quacked enumerator quacks like IEnumerator, the element type is the type of the Current property of the quacked enumerator
Quacking like IEnumerable means it has:
A public non-static unambiguous GetEnumerator method with a class, struct or interface return type
Quacking like IEnumerator means it has:
A public non-static Current property
A public non-static unambiguous MoveNext method with a bool return type
If any condition is not met once a GetEnumerator method is found, an error is produced
If the expression's type implements a single IEnumerable<T>, the element type is T
If the expression implements more than one generic IEnumerable<T>, an error is produced
If the expression implements the non-generic IEnumerable, the element type is object
Otherwise, an error is produced
Your case should fall in the emphasized bullet given the current version.
However, if you're using a previous version and my guess is right, your case is the second to last.
As rcb.Items returns a RadComboBoxItemCollection, i will become of type object.
Either you need to convert it to a RadComboBoxItem
(i as RadComboBoxItem).Checked = true;
Or modify the loop like
foreach (var i in rcb.Items.Cast<RadComboBoxItem>())
{
i.Checked = true;
}
Hope it helps.
Using var is like using type Object in that case since Items is a collection of Object.
If you wish to use the expression i.Checked = true, you must transtype your i into a RadComboBoxItem first.
foreach(RadComboBoxItem i in rcb.Items)
{
i.Checked = true;
}
This would be the best if you wish to use this argument (except if rct.Items contains something else than RadComboBoxItem types, in that case you may have to find the right interface.
I have two types: let's call them A and B. A can be converted to B using an adapter method.
I then have a collection of A's in a List<A> (it could be any collection type that supports IEnumerable<A>).
I now want to convert from IEnumerable<A> to IEnumerable<B>. I know the Type of each of A and B, and I have a method to convert an A into a B, but my method and/or class is not templated itself, so I do not have access to the template type; e.g. the T in IEnumerable<T>.
I effectively want to write this ConvertCollection method, where I know "from" is of type IEnumerable<{something}>:
object ConvertCollection(object from, Type fromType, Type toType, Converter converter);
My converter looks like this:
delegate object Converter(object from);
My attempt leaves me here:
object ConvertCollection(object from, Type fromType, Type toType, Converter converter)
{
return ((IEnumerable<object>)from).Select(converter);
}
which partly works. If I call it like this
ConvertCollection(new List<A>() { new A() }, typeof(A), typeof(B), AToBConverter);
the returned collection does contain a collection of Bs, but the collection itself is of type IEnumerable<object>, not IEnumerable<B>, because I don't know how to cast to IEnumerable<{toType}>. (It matters because the result needs to be serialized).
I can attack it from the other end and create the correct return type like this:
var result = Activator.CreateInstance(typeof(List<>).MakeGenericType(toType));
// TODO: populate result here
return result;
but then the problem is that to achieve the TODO part, I need to call List<> methods on result, but I can't cast it to any type of List<> because of Co/ContraVariance rules, so even though I know the type supports List<> methods, I can't get at them to use them to populate the list; e.g. to use Add().
Is there a way to do this without using 'dynamic' and without too much reflection? I know I could locate and invoke the Add() method via reflection, but it seems like it shouldn't be necessary.
.NET 4.0 BTW
-- Clarification
As Euphoric correctly speculates, and I tried but rather badly failed to convey above, I know the types A and B at runtime, but I do not know them at compile time. Hence the direct use of generics is not an option. I do know that the collections (both supplied and as must be returned) implement the generic IEnumerable<>. That is all fixed and outside my control. (I've adjusted the title accordingly).
** Edit 2: fixed some formatting causing <> to not display (easy to accidentally omit the back-ticks!)
Using the LINQ Select method:
var result = listA.Select(a => Converter(a));
Since you are using .NET 4.0, you really should avoid using object and use generics.
The solution I settled on was to use reflection to invoke the Enumerable.Cast<> method to cast the resultant collection from IEnumerable<object> to the correct IEnumerable<> type. I got the idea from the answer to this question: Convert IEnumerable to IEnumerable<T> when T isn't known until runtime. Seems to involve very little performance penalty.
So the full answer becomes:
object ConvertCollection(object from, Type fromType, Type toType, Converter converter)
{
var partialResult = ((IEnumerable<object>)from).Select(converter);
var castMethod = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(toType);
return castMethod.Invoke(null, new[] { partialResult });
}
Maybe something like this?
IEnumerable<TTo> ConvertCollection<TFrom,TTo>(object from, Converter converter)
{
return ((IEnumerable<TFrom>)from).Select(a=>(TTo)converter(a)).ToList();
}
Then you simply call it:
ConvertCollection<A,B>(new List<A>() { new A() }, AToBConverter);
As at the link: http://msdn.microsoft.com/en-us/library/bb383973.aspx
...An implicitly typed local variable is strongly typed just as if you had declared the type yourself, but the compiler determines the type...
But I have such piece of code:
protected void Page_Load(object sender, EventArgs e)
{
if (Session["user"] == null) Response.Redirect("Default.aspx");
StringBuilder sb = new StringBuilder();
foreach (var item in Session)
{
sb.Append("Session Parameter: [");
sb.Append(item);
sb.Append("]<p />Guid Value: [");
sb.Append(Session[item] + "]");
}
Response.Write(sb.ToString());
}
I'm getting such error in Visual Studio:
Argument 1: cannot convert from 'object' to 'string' for the line:
sb.Append(Session[item] + "]");
But item is identifying at runtime as a string type as I looked in the debugger.
When I have read about var at msdn/in books I thought that var doesn't relate to RTTI-stuff. Compilers just changes the variable with this implicitly type on explicitly type like string, int etc at compile time.
Why did I catch such error?
A var declaration in C# is strongly typed but in this case you are dealing with a non-generic collection type in the value Session. This causes C# to choose object for the type of item and hence you get a later error trying to use item in a position that requires a string.
For non-generic collections you need to still explicitly type the iterator variable in a foreach block
foreach(string item in Session) {
...
}
"Why did I catch such error?" - because sb.Append(Session[item] + "]"); is expecting a string - so you need to cast the Session[item] to a string:
sb.Append(((string)Session[item]) + "]");
as per the MSDN article How to: Read Values from Session State.
In this case, Session is a HttpSessionState implements IEnumerable (though the same would happen if it implemented IEnumerable<object>), so your var gets mapped by the compiler into:
foreach (object item in Session)
{
In this case, this is likely IEnumerable, so you would need to specify the type explicitly in the foreach:
foreach (string item in Session)
{
This is allowed with foreach for non-generic IEnumerable collections.
As for your main question:
C# does var have a strong type?
Yes. In this case, the collection itself does not provide a strong type, so var uses System.Object.
You are correct, var is typed statically (i.e. at compile time). The static type of var, however, comes from the context, which lets the compiler derive the type of the var.
In case of Session which implements IEnumerable, the only type the compiler can derive is object, leading to the error that you describe.
Had Session implemented, say, IEnumerable<string>, the var in the loop would be equivalent to string, not to object.
In your example it comes down to what type of collection Session is. In this case it is a colleciton of objects so the compiler makes var item into object item.
As the other answers have indicated, Session's Item property (which is the default indexer) is of type object. That's why you're getting an object from the indexer - that IS its return type.
http://msdn.microsoft.com/en-us/library/k8s0kehy(v=vs.100).aspx
Its because object is a different type. Pretty much like why a derived class is a different from its base class (ex: class D : B{}). All class types inherit Object, values (struct) I believe do not. They need to be boxed in. The Session collection just says it has a bunch of objects and ANYTHING could be in there. The runtime doesn't try to check unless you ask it to (by typecast or is keyword). (More about the compiler below)
The var in that foreach is saying this variable (item) should be whatever type Session enumerator returns. For the most part specifying a different type there is like a typecast.
Just because something is a class or boxes (which means it is an object) doesn't mean the compiler has any idea of what the object actually is. In fact I think the standards suggest if something becomes an object the compiler should not auto convert it even if it knows what type it actually is (perhaps it knows from a few lines above)
First let me say, that what I want to do is get the value of a property in a generic class that may be overriden by class that inherits from it. Think of it in the base class as a default value, that the inheritor of the class can override to set their own Default value.
I have tried to use reflection directly on the type, using the System.Reflection.FieldInfo.GetValue but this does not work for classes with generic types. So I think that I need to instantiate the class to be able to see what the value is.
The "types" I have I retrieved by reading the Dlls in the bin and using Reflection to find the types that inherit from my interface.
I am using .NET 4.5
here is documentation that seems like it explains exactly what I need to do
http://msdn.microsoft.com/en-us/library/b8ytshk6.aspx
In this documentation the only difference I can see is how we got our types, I looked in the bin for types and they simply called typeof(), Since types are so complex it seems this may be a likely mis-match but I cannot see what is missing(if anything)
foreach (var item in types)
{
var ts = item.GetField("DefaultTimeToExpire");
Type[] typeArguments = item.GetGenericArguments();
if (ts != null)
{
var t = item.MakeGenericType(typeArguments);
var obj = Activator.CreateInstance(t);
var timespan = obj.DefaultTimeToExpire;
subscriberInfos.Add(new Tuple<string, Type, TimeSpan>(item.Name, item, timespan));
}
}
I am calling GetField to look for Items that have a field "DefaultTimeToExpire" so far this part works well to find the type I need.
Next I call GetGenericArguments which returns an expected array of the type Arguments.
then I call MakeGenericType
and finally Create instance wich gives me the error message
"Cannot create an instance of BusinessLogic.TestSubscriberXXX`1[Message] because Type.ContainsGenericParameters is true."
This looks like exactly what I am supposed to do.
Thanks
In order to instantiate a generic type, you need to know the actual values (types) that should be substituted for its type parameters. The GetGenericArguments() method, being a form of reflection, only gives you the type arguments, not their actual values. The values are up to you... that is the entire point of generics.
If item is a type like List<T> then item.GetGenericArguments() will return an array containing a fake "type" representing the type parameter T (with its IsGenericParameter property set to true). Therefore, passing that parameter type back into item.MakeGenericType() will simply create another open generic type equivalent to the original. To close the generic type so that it can be instantiated you need to provide an actual (non-parameter) type argument, such as int.
For example, typeof(List<>).MakeGenericType(typeof(int)) will return typeof(List<int>), while typeof(List<>).MakeGenericType(typeof(List<>).GetGenericArguments()) will simply return typeof(List<>) again. This is what is happening in your code.
I'm sorry if that is a bit opaque, I don't know how else to explain it. The bottom line is that a type like List<T> is only useful if you have a type you want to substitute in place of T.
Here T could be an array or a single object. How can add the array to an arraylist or add a single object to the same arraylist. This gives me a build-time error that the overloaded match for AddRange has invalid arguments.
T loadedContent;
if (typeof(T).IsArray)
{
contentArrayList.AddRange(loadedContent);
}
else
{
contentArrayList.Add(loadedContent);
}
EDIT: Corrected my answer after checking some of the rules around casting to type Array.
All specific types of arrays, such as int[], string[], or MyCustomObject[] derive from the base Array class, and as such, they implement the ICollection interface, which is what the ArrayList.AddRange method accepts as a parameter.
Assuming that your contentArrayList variable is an ArrayList object, you should be able to cast your loadedContent variable to ICollection:
contentArrayList.AddRange((ICollection)loadedContent)
Alternatively, you could combine the check for whether it is an array with the cast:
Array loadedContentAsArray = loadedContent as Array;
if (loadedContentAsArray != null)
{
contentArrayList.AddRange(loadedContentAsArray);
}
The solution provided by Dr. Wily's Apprentice will work but I would like to make some side comments.
You must have design issue in your code if you use generics but you are still committed to a specific data type. Basically you destroy the purpose of the generics, as stated on MSDN:
Generics allow you to define type-safe data structures, without committing to actual data types.
Maybe you should reconsider some refactoring, maybe by adding methods with different parameters or something...
If your contentList is of Type ArrayList, I would go that way.
ICollection contentArray = loadedContent as ICollection;
if (contentArray != null)
contentList.AddRange(contentArray);
else
contentList.Add(loadedContent);