Using Linq to access an objects properties within another object - c#

I need to get a value from an object within an another object. My problem is I can't access any values from within the subobject, i always get the value of the object type itself.
Code where i'm accessing the object
var test = scheduledTask.Fields.Select(x => x.FieldValue);
This brings back in the results view
[0] 10111
[1] {ObjectType.Extension}
I need to access the [1] element which contains the following properties (amongst others), and i need to access the DisplayName
{
DisplayName: "MainMenu",
CategoryId: -1,
Id: 433
}
ScheduledTask is
{
Fields: {Fields.Field[2]},
LastModifiedDate:null,
{Fields.Field[2]}
}

You don't need LINQ to access a specific index of an array.
string name = (scheduledTask.Fields[1].FieldValue as ObjectType.Extension)?.DisplayName;
Since the array contains values of different types I assume that we have an array of object. Therefore we must cast to the expected type to be able to access specific fields or properties.
In case the value is null or the type does not match as will yield null. The null-conditional operators ?. performs a member or element access operation only if an operand is non-null and otherwise return null.
If you don't know the index of the required value, you can query with
string name = (scheduledTask.Fields
.Select(x => x.FieldValue)
.OfType<ObjectType.Extension>()
.FirstOrDefault()
)?.DisplayName;
If you are sure the required value is there and not null, you can drop the ?.

Assuming x.FieldValue is an object you could try casting to check if it is of type ObjectType.Extension:
var test = scheduledTask.Fields.Select(x => {
var asExtension = x.FieldValue as ObjectType.Extension;
if(asExtension != null) return asExtension.DisplayName;
else return x.FieldValue;
});
ETA: The as operator is a sort of safe-cast that will return null if the runtime type of LHS argument doesn't match the static type identified by the RHS argument.

Related

Nullable record structs in C# [duplicate]

So I've got a collection of structs (it's actually a WCF datacontract but I'm presuming this has no bearing here).
List<OptionalExtra> OptionalExtras;
OptionalExtra is a struct.
public partial struct OptionalExtra
Now I'm running the below statement:
OptionalExtra multiOptExtra = OptionalExtras.Where(w => w.Code == optExtra.Code).FirstOrDefault();
if (multiOptExtra != null)
{
}
Now this won't compile:
the operator != cannot be applied to opperands of type OptionalExtra
and '<null>'
After a little googling I realised it's because OptionalExtra is a struct. Which I believe is not nullable unless defined as a nullable type?
So my question is, if my where statement returns no results what will be the outcome of the FirstOrDefault call? Will it thrown an exception?
Incidently this should never happen but better safe than sorry.
If your collection is empty, FirstOrDefault will return default(OptionalExtras). The default value of a struct is the struct with all its values in turn default initialized (i.e. zero, null, etc.).
If you assume that there will be an element and your code doesn't work with an empty collection, Use First() instead, since that will throw an exception when your collection is empty. It's generally better to fail fast than to return wrong data.
If you cannot assume that there will be an element, but also cannot deal with struct default initialization, you might make the structs in the collection a nullable value type, for example as follows:
OptionalExtras
.Where(w => w.Code == optExtra.Code)
.Cast<OptionalExtra?>()
.FirstOrDefault();
This way you can get a null return even for a struct. The key idea here is to extend the set of possible values to include something other than an OptionalExtra to allow detection of an empty list. If you don't like nullables, you could instead use a Maybe<> implementation (not a .NET builtin), or use an empty-or-singleton list (e.g. .Take(1).ToArray(). However, a nullable struct is likely your best bet.
TL;DR;
.FirstOrDefault<T>() returns default(T) if the sequence is empty
Use .First() instead if you assume the list is non-empty.
Cast to nullable and then use .FirstOrDefault<T>() when you cannot assume the list is non-empty.
As others have said, the result of your code when no elements match will be:
default( OptionalExtra )
If you want a null returned, you can cast your list to OptionalExtra?
OptionalExtra? multiOptExtra = OptionalExtras.Cast<OptionalExtra?>().Where( ...
You can then test for null
If default(OptionExtra) is still a valid value, it's better to change your code to this
var results = OptionalExtras.Where(w => w.Code == optExtra.Code).Take(1).ToList();
if (results.Any()) {
multiOptExtra = results[0]
}
The result will be the default value of your struct, e.g. default(OptionalExtras).
Whereas for a reference type the default value is null.
its provide you defualt value for your structure like as below
int[] numbers = { };
int first = numbers.FirstOrDefault();
Console.WriteLine(first);//this print 0 as output
other option to handle is make use of default value like as below
List<int> months = new List<int> { };
// Setting the default value to 1 by using DefaultIfEmpty() in the query.
int firstMonth2 = months.DefaultIfEmpty(1).First();
Console.WriteLine("The value of the firstMonth2 variable is {0}", firstMonth2);
If you want to check for null, use System.Nullable collection:
var OptionalExtras = new List<OptionalExtra?>();
/* Add some values */
var extras = OptionalExtras.FirstOrDefault(oe => oe.Value.Code == "code");
if (extras != null)
{
Console.WriteLine(extras.Value.Code);
}
Note that you have to use Value to access the element.
Assuming Code is a string for the purposes of my answer, you should be able just to test that value for its default.
OptionalExtra multiOptExtra = OptionalExtras.Where(w => w.Code == optExtra.Code).FirstOrDefault();
if (multiOptExtra.Code != null)
{
}

Nested LINQ with multiple lists

I think this is just a personal code problem, but I just can't see how I'm supposed to return the IsIdentifier bool from this nested object. dp has a list of dd's, and dd has a list of supportedformats, and one supportedformat has a list of parameters, and in parameter is a property (bool) called isIdentifier and I want to return it.
parameters.Add(new SupportedParameter
{
Name = name,
Version = version,
IsIdentifier = dp.Where(dp => dp.dd
.Where(dd => dd.SupportedFormats
.Where(sf => sf.Parameters.Where(sp => sp.Name == name).FirstOrDefault().IsIdentifier)
)
)
}
);
Errors:
Cannot implicitly convert type IEnumerable<SupportedFormat> to bool.
Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate type
Also can somebody tell me how to format the code on stackoverflow? Because I'm on a different pc and usually TAB and SHIFT + TAB do the trick. But it didn't work so I tried CTRL + [ or CTRL + ] and these don't work either.
From looking at your code, it appears that you want to find the first deeply nested Parameter with Name == name and then return its IsIdentifier boolean property.
You can reduce the complexity of this operation by "flattening" out all the nested collections, so you'll end up with one big collection containing every Parameter across all the parent objects, by using the LINQ SelectMany extension method.
Here's how I would approach your specific case:
...
IsIdentifier = dps
.SelectMany(dp => dp.dds)
.SelectMany(dd => dd.SupportedFormats)
.SelectMany(sf => sf.Parameters)
.FirstOrDefault(parameter => parameter.Name == name)?.IsIdentifier ?? false;
...
The ?.IsIdentifier ?? false part is just a fallback in case there are no Parameters with the specified name - it will just set IsIdentifier to false in that case.

Object does not match target type PropertyInfo SetValue - one class to another

So I have 2 classes, both have identical Property names. One class contains different variables: int, strings, bool and DateTime The second class contains only 1 int and the rest are all strings.
Now I want to loop through all the properties, get the value from class1, encrypt that data and save it as a string in obj2, then return it to the main form (to save it in a database later).
public PersoonEncrypted EncryptPersonClass(Class1 object1)
{
PersoonEncrypted persEncrypt = new PersoonEncrypted(); //second class obj
Type type = object1.GetType();
PropertyInfo[] properties = type.GetProperties();
Type type2 = persEncrypt.GetType();
PropertyInfo[] properties2 = type.GetProperties();
foreach (var bothProperties in properties.Zip(properties2, (obj1, obj2) => new { Obj1 = obj1, Obj2 = obj2 }))
{
string value = "";
value = bothProperties.Obj1.GetValue(object1) as string;
if (!string.IsNullOrWhiteSpace(value))
{
string encryptValue = Encrypt(value);
if ((bothProperties.Obj2 != null) && (bothProperties.Obj2.PropertyType == typeof(string)))
{ //!= null check has no effect at all
bothProperties.Obj2.SetValue(persEncrypt, encryptValue, null); //errorLine
}
}
}
return persEncrypt;
}
That is what I came up with until now.
I have, of course, searched for other solutions like this one. This, after applying some own changes, didn't return any errors, but it didn't save any encrypted strings into the class persEncrypt. What I concluded was, from that test, is that it was testing if the value in the second class(persEncrypt in my example) from the particular property was null, while it shouldn't do that, it should make a new instance of that variable and save it in the object class, but removing that check gave me the same error.
you're just .Zip-ing the two lists of PropertyInfo objects, which simply iterates through both lists and doesn't check or sort for any sort of matching. This could result in erroneous behavior depending on the order in which properties appear - consider using a .Join instead to match property names.
This code doesn't check for an indexer on the property before attempting to assign to it without one - any indexed property which is of type string will make it to this point and then throw an exception when you try to set it.
Because this code is calling into Properties, there's the possibility an exception is being thrown by the code of the Property itself. This is where a StackTrace from your exception could reveal much more about what's happening.
Your code also checks for a property of type string directly - when using reflection you should use IsAssignableFrom instead in order to allow for inherited types, though that is unlikely the issue in this one case.

How to handle type “Object {System.Collections.Generic.List<object>}”

This is my first time encountering with such an object.
>Link of image of my local window during debug<
So to put it very simply, how do I access the value for CardNO or ItemID, which is 296 and 130 respectively when the only methods 'test' give are exactly like a normal object and I don't know what to cast it to. I can't even do this 'test[0]'.
This is where 'test' comes from:
private void ListBoxIssue_OnDragEnter(object sender, DragEventArgs e)
{
var test = DragDropPayloadManager.GetDataFromObject(e.Data, typeof(CommsItem));
}
Use
var item = (CommsItem)((List<object>)test).FirstOrDefault();
Be sure to check first if test is an instance of List<object> before casting, and if test[0] is an instance of CommsItem.
You need to cast your List<Object> to List<CommsItem>.
Example:
var test = DragDropPayloadManager.GetDataFromObject(e.Data, typeof(CommsItem)).Cast<CommsItem>().ToList();
Or cast each individual element:
CommsItem element = (test[0] as CommsItem);
Which will return the element casted to CommsItem, unless it is not of or derived of that type, in which case it will return null.
So to answer your question, you can access them as:
string CardNO = (test[0] as CommsItem).CardNO;
or
var test = DragDropPayloadManager.GetDataFromObject(e.Data, typeof(CommsItem)).Cast<CommsItem>().ToList();
string CardNO = test[0].CardNO;
(If you use the first method, you do not need the cast)
What you are doing is casting your List<object>, test, to a List<CommsItem>, that you can use to access the properties, or simply casting each item.
More simply:
var item = ((List<object>)test).Cast<CommsItem>().FirstOrDefault();
If your list is not a list of CommsItem, then you'll get an empty list back. FirstOrDefault() ensures that you get the first item only, and if there is no first item, then the default value for the item (for a reference object, this would be null).

Casting to object from a collection vs using foreach

I am a little confused about the difference between the two statements.
This one works and prints out the property results just fine.
foreach( string key in result.Properties.PropertyNames )
{
foreach( object property in result.Properties[key] )
{
Console.WriteLine("{0}:{1}", key, property.ToString());
}
}
and the following doesn't work. I thought by casting the specific property to object it would be the same thing but its obviously not:
foreach( string key in result.Properties.PropertyNames )
{
if( key == "name" )
{
Console.WriteLine("{0}:{1}", key, ((object)(result.Properties[key])).ToString() );
}
}
I get the object type of result.Properties[key] printed instead.
The two snippets do completely different things.
In the first example, result.Properties[key] is a collection of some sort (IEnumerable). It's looping through each object in the collection and printing the string representation of that object (ToString()) out to the screen.
In the second example, it's just printing the string representation of the collection itself, which often times is just the name of the type.
NOTE: You would almost never want ToString contain out the contents of the collection.
The difference is, even though property is type object in the first example, The actual property still has an underlying type that is being used for ToString(). You're just using the object type to hold a more derived type.
In the second example, where you cast it to type object you're telling the compiler "I don't care what type the property actually is, treat it like an object so it ends up using object.ToString() instead of property.ToString().
Properties is an object ith property names and per property name a set of values.
So... result.Properties[key] will return a ResultPropertyValueCollection, which contains one or more values.
You can enumerate through these values which you do in your first piece of code.
In your second example, your want to show all values in this collection. This can easily be done with LINQ. I split up the code for better understanding, but it can be written in one line:
foreach( string key in result.Properties.PropertyNames )
{
if( key == "name" )
{
IEnumerable collection = result.Properties[key];
string[] seperateValues = collection.Cast<object>().Select(o => o.ToString()).ToArray();
string joinedValues = String.Join(", ", seperateValues)
Console.WriteLine("{0}:{1}", key, joinedValues);
}
}

Categories

Resources