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

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.

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

Check if object of type X is in list and update corresponding field fails for reasons I don't understand

I'm trying to check if a list of Quests contains a Quest of Type StartingQuest. If it does, I want to increase the field OgresKilled that belongs to the Quest by 1.
The following code does not work.
I get the error message in the second line of code where I check ... == StartingQuest:
Error CS0119 StartingQuest is a type, which is not valid in the
given context
This does not make sense to me, as I explicitly want to check against the type.
if (Globals.ActiveQuests.Contains(new StartingQuest()))
{
StartingQuest a = Globals.ActiveQuests.Find(a => a.GetType == StartingQuest);
a.OgresKilled += 1;
}
Can someone please explain what my mistake is? And is this an efficient way of doing what I want to do?
Other answers have explained aspects that are incorrect in the code in the question:
It checks for equality between an existing quest and a brand-new StartingQuest instance, which is unlikely to ever be true
It tries to use the method group GetType directly, instead of calling the method (as a.GetType())
It tries use a type name directly in the == expression, where you intended to evaluate a Type reference - typeof will help there.
I would suggest a slight modification of SomeBody's answer (in fact explaining an option already present in a comment).
Currently both the question and all the answers here are checking for the exact type StartingQuest, which is somewhat brittle. It's usually more useful to check whether something is a target type or a derived type, even if you don't currently have any derived types.
LINQ provides the OfType method which will find elements of a sequence in precisely that manner - and the resulting sequence has an element type of that "target type", which means that if you need to access something that's in StartingQuest but not the base type (Quest or whatever it is), you'll be fine.
So I'd definitely use:
// Import the System.Linq namespace to make the OfType and FirstOrDefault
// extension methods available
using System.Linq;
...
var startingQuest = Globals.ActiveQuests.OfType<StartingQuest>().FirstOrDefault();
if (startingQuest is object)
{
startingQuest.OgresKilled++;
}
The use of is object instead of != null is just to use the more modern idiom for checking whether something is non-null.
Your if check will always fail, because you look for a new instance of StartingQuest. If it is a reference type, this will never be true, because the newly created instance will have a different reference than all the quests in the list. Moreover, you are iterating the list twice, one time in the Contains method, and one time in the Find method. This will fix these issues:
var startingQuest = Globals.ActiveQuests.FirstOrDefault(x => x.GetType() == typeof(StartingQuest));
// as an alternative, this is also possible:
// var startingQuest = Globals.ActiveQuests.OfType<StartingQuest>().FirstOrDefault();
if(!(startingQuest is null))
{
startingQuest.OgresKilled += 1;
}
FirstOrDefault returns the first instance that matches a criterion. If no item is found, the default value of that type is returned (null for reference types). Hence you check whether the return value is not null. If it's not null, an item was found and you can increment your OrgresKilled variable.
Your error is because you have to get the type of StartingQuest:
a.GetType() == typeof(StartingQuest)
GetType returns a Type, so you also need an instance of the type Type to compare to. That's what typeof does, it returns an instance of Type that corresponds to the type of its argument.

Casting to custom type, Enumerable.Cast<T> and the as keyword

This is more a question out of curiosity than necessity and came about having had to deal with Active Directory (MS) types such as SearchResultCollection (in the System.DirectoryServices namespace).
Frequently when dealing with AD in code, I find that I'm having to check values for null, Count, [0] .. whatever and convert what I get out .. all the while hoping that the underlying AD object via COM doesn't go poof etc.
After having a play about with Parallel.ForEach recently - and having to pass in an IEnumerable<T>, I thought, maybe it would be fun to see how I could cast a SearchResultCollection to an IEnumerable of my own custom type. In this type I would pull out all the values from the SearchResult object and stick them in my own, .NET managed code. Then I'd do away with the DirectoryEntry, DirectorySearcher etc. etc.
So, having already worked out that it's a good idea to do searchResultCollection.Cast() in order to supply Parallel.ForEach with it's source, I added an explicit operator for casting to my own type (let's just call it 'Item').
I tested this out within the ParallelForEach, var myItem = (Item)currentSearchResult.
Good times, my cast operator is called and it all works. I then thought, it would be nice to do something like searchResultCollection.Cast<Item>(). Sadly this didn't work, didn't hit any breakpoints in the cast operator.
I did some Googling and discovered a helpful post which Jon Skeet had answered:
IEnumerable.Cast<>
The crux of it, use .Select(...) to force the explicit cast operation. OK, but, hmmm.
I went off and perhaps disassembled System.Core -> System.Linq.Enumerable.Cast<TResult>, I noticed that this 'cast' is actually doing an 'as' keyword conversion under the hood:
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
if (enumerable != null)
{
return enumerable;
}
if (source == null)
{
throw Error.ArgumentNull("source");
}
return CastIterator<TResult>(source);
}
I read some more and found this:
Implicit/Explicit conversion with respect to the "as" keyword
The top answer here states that 'as' doesn't invoke any conversion operators .. use a (cast). Semantically I find this a little weird, since the extension method is called cast.. Shouldn't it be casting? There will no doubt be a really good reason why this doesn't happen, anyone know what it is?
Even if it would have used the cast operator instead of as it still wouldn't be invoking user defined explicit operators, so it wouldn't matter. The only difference would be the type of exception thrown.
Explicit operators aren't known at all by the runtime. According to the CLR there is no way to cast your search result to Item. When the compiler notices that there is a cast that matches a given explicit operator it injects at compile time a call to that explicit operator (which is basically a static method) into the code. Once you get to runtime there is no remaining knowledge of the cast, there is simply a method call in place to handle the conversion.
Because this is how explicit operators are implemented, rather than providing knowledge to the runtime of how to do the conversion, there is no way for Cast to inject the explicit operator's call into the code. It's already been compiled. When it was compiled there was no knowledge of any explicit operator to inject, so none was injected.
Semantically I find this a little weird, since the extension method is called cast.. Shouldn't it be casting?
It's casting each element if it needs to, within CastIterator... although using a generic cast, which won't use explicit conversion operator you've defined anyway. You should think of the explicit conversion operator as a custom method with syntactic sugar over the top, and not something the CLR cares about in most cases.
For for the as operator: that's just used to say "If this is already a sequence of the right type, we can just return." It's used on the sequence as a whole, not each element.
This can actually cause problems in some slightly bizarre situations where the C# conversion and the CLR conversions aren't aligned, although I can't remember the example I first came upon immediately.
See the Cast/OfType post within Edulinq for more details.
If I understand correctly you need a DynamicCast which I wrote sometime ago.
Runtime doesn't know about implicit and explicit casting; it is the job of the compiler to do that. but using Enumerable.Cast<> you can't get that because Enumerable.Cast involves casting from System.Object to Item where there is no conversion available(you have conversion from X to Item, and not Object to Item)
Take the advantage of dynamic in .Net4.0
public static class DynamicEnumerable
{
public static IEnumerable<T> DynamicCast<T>(this IEnumerable source)
{
foreach (dynamic current in source)
{
yield return (T)(current);
}
}
}
Use it as
var result = collection.DynamicCast<Item>();
It is casting. See the implementation of CastIterator.
static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source) {
foreach (object obj in source) yield return (TResult)obj;
}
The use of the as keyword here is only to check if the entire collection can be casted to your target instead of casting item by item. If the as returns something that isn't null, then the entire collection is returned and we skip the whole iteration process to cast every item.
For example this trivial example would return a casted collection instead of iterating over every item
int?[] collection = ...;
var castedCollection = collection.Cast<int?>()
because the as works right off the bat, no need to iterate over every item.
In this example, the as gives a null result and we have to use the CastIterator to go over every object
int?[] collection = ...;
var castedCollection = collection.Cast<object>()

Fetching values from an object with nested objects

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;

Casting woes with generic class which needs to know the specific value of T (out of a possible 30 different types) - is this scalable?

I am working with a 3rd party web testing API which has a Src property for image.
What I need to do is say:
if (t is HtmlImage) { // Do cast here. } // t is a variable of type T (my generic).
However, my cast does not work. The cast is as follows:
Controls.HtmlImage img = (Controls.HtmlImage)t;
This gives an error stating that I cannot convert Type 'T' to HtmlImage. BUT type T is the base class for all controls, including HtmlImage.
The problem I am trying to solve is I have a utility method to loop through a site's pages, but if I am passing Html Image as the value of T, I get the src property as I need the paths (to identify which images have no alt tags, and the src property can never be null). If T is of another type, I will get another property as an identifier. I am testing if images have alt tags, links have meaningful descriptions, etc. For a possible type total of about 30, is this scalable? Because I will be saying if T is Button, else, etc etc for quite a lot of types (could use table driven method).
HtmlImage inherits from:
public class HtmlImage : ArtOfTest.WebAii.Controls.HtmlControls.HtmlControl
T is of type HtmlControl
Thanks
You can't cast because the compiler doesn't necessarily know what a cast would mean. Casts can be used for boxing, unboxing, user-defined conversions or straight reference type conversions. The latter is what you're most interested in.
On the other hand, "as" works because it's always just a reference type conversion (unless you use it with a nullable type as the right hand operand).
In fact, you can use a cast, but only if you go through object first (which would always be either a boxing conversion or a reference conversion, but never a user-defined conversion):
HtmlImage hi = (HtmlImage) (object) hi;
I'd use as though, personally...
To answer my own question, this doesn't through a design time error:
Controls.HtmlImage img = t as Controls.HtmlImage;
But why doesn't the () operator work normally?
The other question left is the scalability of this sort of approach. Is there another, better, way?
I am not sure to understand. Try to add where T : HtmlImage in your methode definition
Controls.HtmlImage img = t as
Controls.HtmlImage;
But why doesn't the () operator work
normally?
When using the "as" keyword, a cast is invoked but when this doesn't succeed, like when img == null then it will return null. A cast through () will generate a exception when the cast didn't succeed.

Categories