Fetching values from an object with nested objects - c#

I'm using a service where I don't know the actual structure of the response. Therefore I'm returning it as an object:
object result = Service.GetStuff();
If I inspect it in the debugger, it looks something like this:
I've tried casting it to dynamic, Hashtable and Arraylist, without success. How do I access the properties on the object? Similar SO questions haven't helped me.
Cannot apply indexing with [] to an expression of type `object'

If the result is always an array (or something that implements IList, like an ArrayList), you can cast it to an IList, which is where the index operator is defined:
IList result = Service.GetStuff() as IList;
Then you can apply the index operator. If it's not always an array then you could use reflection to determine if it is an array, then cast it.
The bigger question is, what are you going to do with the objects since you don't know what they are?

It seems that it is some sort of dictionary try casting the object to IDictionary. If that does not help, you may call the result.GetType() to see what is the actual type and cast to that.
I also think that dynamic should work too.
dynamic result = Service.GetStuff();
int reconnectDelay = ((dynamic)result[0]).reconnectDelay;

Related

Casting object to List<(Enum, string)> issue

I am creating a converter by implementing IMultiValueConverter with *Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
I am passing a List<(SomeEnumType, string)> tuple.
via MultiBinding and on the converter side I would like to cast but it throws a casting error.
I tried :
var result = (List<(Enum, string)>)values[1];
but I got this casting issue:
'Unable to cast object of type 'System.Collections.Generic.List1[System.ValueTuple2[Vasco.Basics.Contracts.CoreConfigurations.Enums.ApplicationType,System.String]]' to type 'System.Collections.Generic.List1[System.ValueTuple2[System.Enum,System.String]]'.'
It is strange because If I pass only one element of SomeEnumType and try to case like (Enum)values[1] casting works well.
When I pass a List<SomeEnumType> and try to cast like (List<Enum>)values[1] does not work already.
Thank you in advance!
When I pass a List and try to cast like (List)values1 does not work already.
You generally aren't allowed to cast generic collections like List<T> or IEnumerable<T> to other types. This comes down to how C# and the compiler handle generics and something called Covariance and contravariance. This is an incredible complicated topic, at least for me, so I won't bogg you down with the fine details.
Consider the following situation.
List<string> strings = new() { "Kitten", "Mouse", "horse" };
List<object> objs = strings;
This may seem pretty natural, especially if you try to explicitly cast the strings list such as (List<object>)strings, but this wont compile and that is a good thing! It protects you from doing silly things, like for example:
List<string> strings = new() { "Kitten", "Mouse", "horse" };
List<object> objs = strings;
objs.Add(1.29d);
this may seem like it's only tangentially related to you question, but this is really important, and is the exact reason you cant cast a collection to a different kind of collection, even if you know that they're very similar.
When we add that double to the objs list (assuming that this would compile, it doesn't), what were doing effectively is adding a double to a List<string> which would break everything about how strongly typed languages such as C# work.
It is strange because If I pass only one element of SomeEnumType and try to case like (Enum)values1 casting works well.
The reason you can do this, but not collections, is becuase with a single object the compiler can check to see if there is a valid conversion and do the conversion for you manually. Unlike with collections where the compiler, if it did the same thing as it did with single objects, it would add things to collections that may not match the type that was constrained when that collection was initialized.
Credit to John Skeet for this explanation, Ch4.4.1 ISBN 9781617294532
In general you cannot cast Lists like this - because what you are actually trying to do is cast each item in the list, rather than the list itself. Therefore you'd need to loop through and cast each item individually, like so:
var input = new List<(SomeEnumType, string)>();
// now add items to the input list
var result = new List<(Enum, string)>();
foreach (var element in input)
{
result.Add(
((Enum)element.Item1, element.Item2)
);
}
Remember, a tuple is not a single element but a wrapper for multiple elements, which each need casting.
Or, you could use a tool that allows you to 'map' types, e.g. Mapster or AutoMapper - I personally prefer Mapster.
using Mapster;
var input = new List<(SomeEnumType, string)>();
// now add items to the input list
var result = input.Adapt<List<(Enum, string)>>();
// Adapt<>() is an extension method provided by Mapster

How to convert Array of objects into an IEnumerable of inheriting object?

I have PInfo2[] pInfo and need to use it as an IEnumerable<PInfo3> betterInfo where public class PInfo3 : PInfo2
What I've tried, but doesn't work:
(IEnumerable<PInfo3>)pInfo //Runtime error: "Unable to cast object of type 'PInfo2[]' to type 'System.Collections.Generic.IEnumerable`1[
IEnumerable<PInfo3> betterInfo = pInfo as IEnumerable<PInfo3>; //Always null after cast.
What am I doing wrong, aka how am I being dumb? Do I need to for-loop through the array? I don't know why but I was hoping I didn't have to do that.
You can use LINQ to cast the objects of the enumeration to a different related type.
pInfo.Cast<PInfo3>();
Now, this can cause some issues if there are some elements in the collection that can't be cast, but there are ways around that as well (see Edit):
pInfo.Select(p => (PInfo3)p).Where(p => p != null);
The Cast should be used when you know that the conversion will succeed, and the second when there could be elements that can't make the conversion.
EDIT
Per Daniel A. White's comment below, there is also the .OfType<T>() method, which will only get the items that match the requested type, and avoid the IllegalCastException that Cast<T>() will throw.
For additional details, see this answer.

Convert IEnumerable<A> to IEnumerable<B> without knowing types at compile time

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);

Add a List<object> or Arraylist to an array of CustomObject[]

I have tried many ways like
Cast<CustomObject>, as Customobject and ToArray(Customobject) but nothing worked.
How can I add List or ArrayList via AddRange to a CustomObject[] Array?
Code is really difficult.
But if you have some time you can get the complete source of the destination list from here:
http://www.codeproject.com/Articles/4012/C-List-View-v1-3?msg=3844172#xx3844172xx
This is a Custom Listview
I activated a combobox for the second column, so I can select diferrent values for a cell.
But before this, I have to add something to select.
This is the hole problem.
Update:
Firstly, thanks for the help !
Secondly, Found a solution in the comments from the website with the source.
Had to add some code and changed the destination custom array to a List
list.Cast<CustomObject>().ToArray()
Will work as long as the things in the list are actually CustomObject. If they might be other types, you can use OfType<CustomObject>() instead of Cast. This will filter out anything of an incompatible type.
Assuming the objects really are instances of CustomObject, use LINQ Select method:
objList.Select(o => o as CustomObject).ToArray();
Otherwise you will get an array of null.
If its a List<CustomObject> then let us say
CustomObject[] coarr = list_of_customobject.ToArray();
If its an ArrayList then
CustomObject[] coarr = arraylist.OfType<CustomObject>().ToArray();
If you are unsure whether all of your objects are of the type CustomObject try
var result = list.OfType<CustomObject>.ToArray();
Strictly speaking you cannot add elements to an array, since an array's length remains constant over its lifetime. There are two things you can do:
Create a new array
myArray = myTList.ToArray() // generic)
myArray = myArrayList.Cast<CustomObject>().ToArray() // cast, non-generic
myArray = myArrayList.OfType<CustomObject>().ToArray() // filter by type, non-generic
Set elements of an array
myArray[x] = myTList[y] // generic
myArray[x] = (CustomObject)myArrayList[y] // non-generic
I recommend you to take the generic collection whenever possible. They provide you additional type safety. Casting object variables cause runtime errors you could detect at compile time by using generic types.
If you actually want to add elements to an existing collection, you may try to use a dynamic collection type rather than an array: List<T> : IList<T> or LinkedList<T> : ICollection<T> are a good point to start, or maybe more specific types like Stack<T> or Queue<T>.

Different action on parameter that could be array

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);

Categories