How to retrieve object properties with LINQ? - c#

Let's say I have this object:
public class Foo
{
public string FirstProp {get;set;}
public string SecondProp {get;set;}
public string ThirdProp {get;set;}
}
Now I would like to retrieve only the FirstProp and the SecondProp from that object and concat all the property values into one string.
I have one solution in mind which would't be clean imo. Here it is:
var foo = new Foo("test1","test2","test3");
var propertyNames = new[] {"FirstProp", "SecondProp"};
var properties = foo.GetType().GetProperties().Where(x => propertyNames.Contains(x.Name));
//Then loop through each retrieved property and concat the string
So basically I am just looking for a cleaner solution where I wouldn't be dependent on an array of string.

var foo = new Foo(...);
var result = string.Join(",", new[]{ foo.FirstProp, foo.SecondProp });
Does this suffice? Or do you need Reflection for dynamic typing? If so, one can also supply MemberExpressions to dynamically get the values. Are you dealing with a collection of instances? Do you need this functionality extracted in a helper-method with a parameter for the needed properties?

Related

How to deserialize json and retrieve specific property values in c#

I'm quite new to JSON with C# (Using VS2017). Tried accessing each element of this object via code (e.g. Getting the strings "Obj1", "Obj2", "Obj3" and then the values of each of their members (Id and name).
I do not know in advance how many "ObjX" there will be or their names. I'm trying to load this list into some class and then convert it into a CSV (or SQL inserts).
Tried with JSON.net and JsonFx but I think my skills are just not strong enough to understand how to do this other than brute-force string manipulation functions. Can anyone help?
{
"OBJ1":{
"id":1,
"name":"Name1",
},
"OBJ2":{
"id":2,
"name":"Name2",
},
"OBJ3":{
"id":3,
"name":"Name3",
}
}
Create a class, MyClass with two properties, int Id and string Name.
public class MyClass
{
public int Id {get; set;}
public string Name {get;set;}
}
Then, depending on how you want to do it you can either deserilize it to a Dictionary or create a MyClassRoot object with three MyClass properties.
I recommend the Dictionary approach.
If you use the Dictionary approach your code will still work if more properties gets added to the JSON. If you use the MyClassRoot solution you will need to add the corresponding property to the MyClassRoot if the json updates.
Then with JSON.Net you can deserialize the object like this.
var result = JsonConvert.DeserializeObject<Dictionary<string, MyClass>>(json);
The "OBJ1", "OBJ2" and so on will then be keys in the dictionary and you can access the values like this:
var obj1 = result["OBJ1"];
var obj1Name = obj1.Name;
var obj1Id = obj1.Id;
To get all the MyClass objects to a list, simply do the following:
var list = result.ToList();
MyClassRoot approach(not recommended at all, just a POC):
public class MyClassRoot
{
public MyClass Obj1 {get;set;}
public MyClass Obj2{get;set;}
public MyClass Obj3{get;set;}
}
var result = JsonConvert.DeserializeObject<MyClassRoot>(json);
var obj1Name = result.Obj1.Name;
var obj1Id = result.Obj1.Id;

Linq Except considering only one property

I have two lists of object.
List<object1> obj1 = new List<object1>();
List<object2> obj2 = new List<object2>();
I want to do this:
obj2 = obj2.Except(obj1).ToList();
However, by reading other questions similar to mine, I understand that this doesn't work unless I override Equals.
I do not want to do that, but both obj2 and obj1 have a string property that is sufficient to see whether they are equal. If obj2.StringProperty is equivalent to obj1.StringProperty then the two can be considered equal.
Is there any way I can use Except, but by using only the string property to compare?
The Except method requires that the two collection types involved have the same element type. In this case the element types are different (object1 and object2) hence Except isn't really an option. A better method to use here is Where
obj2 = obj2
.Where(x => !obj1.Any(y => y.StringProperty == x.StringProperty))
.ToList();
In .Net 6 you can use .ExceptBy() from System.Linq.
If your classes contain the following properties:
public class Object1
{
public string String { get; set; }
}
public class Object2
{
public string String { get; set; }
}
.ExceptBy() can be used like this to compare the two string properties:
var objects1 = new List<Object1>();
var objects2 = new List<Object2>();
// Populate lists
objects2 = objects2
.ExceptBy(
objects1.Select(obj1 => obj1.String)
obj2 => obj2.String) // selecting the String property of Object2 for comparison
.ToList();
Example fiddle here.
(Using .ExceptBy() has also been suggested by #mjwills in their comment, via MoreLinq.)

How to extract an object name

i've got a class filled with lists of subclasses:
public class ClassOfKb
{
public List<Data> KbDatas {get;set;}
public List<Product> KbProducts {get;set}
}
public class Data
{
public Guid ID {get;set;}
public byte[] data {get;set;}
public string Name {get;set;}
}
public class Product
{
public Guid ID {get;set;}
public string Name {get;set;}
public byte[] Image {get;set;}
}
i create an object:
ClassOfKb kb = new ClassOfKb
now i'd like to extract the string "Datas" from the sub-object kb.KbDatas, I tried:
string name = kb.KbDatas.GetType().BaseType.Name.Substring(2);
aswell as:
string name = kb.KbDatas.GetType().Name.Substring(2);
but nothing gave me what I need, is there any way to do this?
EDIT: to specify my question, the string I need is the name of the list, except the first two letters! KbDatas => Datas
EDIT2: i did a mistake, the list-names and class-names are different and i need the list-name
You can use Type.GetGenericArguments to solve this
ClassOfKb kb=new ClassOfKb();
kb.KbData = new List<Data>();
string nameOfData = Type.GetType(kb.KbData.ToString()).GetGenericArguments().Single().Name;
OUTPUT : nameOfData = Data
kb.KbProduct = new List<Product>();
string nameOfProduct = Type.GetType(kb.KbProduct.ToString()).GetGenericArguments().Single().Name;
OUTPUT : nameOfProduct = Product
Since that's a collection it is likely that there are multiple Data objects in it, each with a name. You can use String.Join to concat them with a separator:
string names = string.Join(",", kb.KbData.Select(d => d.Name));
If there's just one object you don't get a comma at the end. If there's no object you get an empty string.
erm, since you have a List of Data there will be a sequence of Names.
IEnumerable<string> names = kb.KbData.Select(d => d.Name);
maybe you want just the first one?
string firstName = kb.KbData.First(d => d.Name);
Try this one
string name = kb.KbData[0].Name.Substring(2);
From the sounds of what you've written, you're looking to get the name of the type in the List instance KbData?
If so, I think this may be what you're looking for: https://stackoverflow.com/a/1043778/775479
If you are trying to get the name of the property. There are several methods for doing so.
Get the name of the generic argument from the property itself - If you know the name of the property.
ClassOfKb kb = new ClassOfKb()
{ KbData = new List<Data>(), KbProduct = new List<Product>() };
Console.WriteLine(kb.KbData.GetType().GetGenericArguments()[0].Name);
Get the name of the property from reflection, if you know the data type of the property.
System.Reflection.PropertyInfo pi = kb.GetType()
.GetProperties()
.FirstOrDefault(p=>p.PropertyType == typeof(List<Data>));
Console.WriteLine(pi.Name.Substring(2)); // ignoring the kb prefix
You can achieve this with reflection. This is example without any checks - just show the mechanism:
PropertyInfo propertyInfo = typeof(ClassOfKb).GetProperty("KbData");
Type propertyType = propertyInfo.PropertyType;
Type genericArgument = propertyType.GenericTypeArguments[0];
string name = genericArgument.Name;
Because property KbData is generic List<Data> you need ask for generic arguments of property type: propertyType.GenericTypeArguments[0] and you should test if the type is really generic by genericArgument.IsGenericType and check generic arguments count
If you need the property name than you can use Expression.
The code below define function for extract name prom a property:
public string GetPropertyName<T>(Expression<Func<T>> property)
{
return ((MemberExpression)property.Body).Member.Name;
}
This converts property to property name string:
GetPropertyName(()=>k.KbDatas).Substring(2)

Passing expression into lambda

I have such class:
public class SomeClass
{
public string Text1 { get; set; }
public string Text2 { get; set; }
public int Number { get; set; }
}
And I have list of this classes objects:
List<SomeClass> myList = new List<SomeClass>();
I want to query this list using LINQ (lambda syntax):
var result = myList.Where(obj => obj.Text1 == "SomeString");
Is there any way to pass property(eg. by string name), by which I want this LINQ query to be performed? In this example, I search by Text1 property, but let's say I want to invoke this search dynamically on Text1 or Text2 (determined in runtime). I want to be able to pass property name, on which this search is performed, and check whether this property is string, so that I'm sure this search CAN be performed first.
Is that possible? I know Reflections and Expressions have something to do about it, but I don't know them very well.
Thanks
The approach using Reflection:
var result = myList.Where(obj => obj.GetType()
.GetProperty("Text1")
.GetValue(obj)
.Equals("SomeString"));
With this way you can change from "Text1" to "Text2" property.
Another approach you can use dynamic linq:
var result = myList.AsQueryable().Where("Text1=#0", "SomeString");
Dynamic LINQ is also available via nuget.
You could use expression-trees?
string memberName = "Text1", value = "SomeString";
var p = Expression.Parameter(typeof(SomeClass), "obj");
var predicate = Expression.Lambda<Func<SomeClass, bool>>(
Expression.Equal(
Expression.PropertyOrField(p, memberName),
Expression.Constant(value,typeof(string))
), p);
var result = myList.AsQueryable().Where(predicate);
or alternative for the last line:
var result = myList.Where(predicate.Compile());

How can I search for a specific struct value? Maybe a better approach?

I'm trying to find a struct I created earlier that has a specific value. Once I found it, I want to set variables on that struct. I don't know how to do this. Is there a better way of doing this? Maybe classes? Or should structs work?
For example, my struct:
public struct MyTest
{
public string device;
public string status;
public string revision;
public string number;
public string ledmo;
}
My Test Code:
MyTest thisTest=new MyTest();
thisTest.device=blah;
thisTest.number=blah2;
MyTest thisTest2=new MyTest();
thisTest2.device=blah5;
thisTest2.number=blah6;
//Another Part in my code.
//Need to find the MyTest Structure that 'device' variable = the string 'blah'
var Foundit=MyTest.find(device==blah);
Foundit.revision=blah9999;
I'd use a class, because Mutable structs are evil
Basically, because every struct is copied, even if you do find the right struct, you'll only ever change one copy. Lets say MyTest.find finds thisTest2 what happens is this
var Foundit = MyTest.Find(device==blah);
// The line above has made a copy of thisTest2, that copy is in FoundIt
Foundit.revision = "blah9999";
// You've changed revision in the copy of thisTest2,
// therefore the contents of thisTest2 remain unchanged
To do this with a class you'll need to keep every instance of the class you create in a list or other data structure, so you know you can look it up.
If you do this you also need to tell the list when you're finished with each object, otherwise they'll hang around forever and never get garbage collected.
Before I go any further, are you sure this is the best way to solve this problem?
Anyway, say your class is MyData, you can put a static factory method on this called Create, which will put each new MyData object into a list.
public class MyData
{
private static List<MyData> allMyDatas = new List<MyData>();
public static IEnumerable<MyData> AllInstances
{
get {return allMyDatas;}
}
public string Device {get; set;}
public string Status {get; set;}
public string Revision {get; set;}
public string Number {get; set;}
public string Ledmo {get; set;}
private MyData() // Private ctor ensures only a member
{ // function can create a new MyData
}
public static MyData Create()
{
var newData = new MyData();
allMyDatas.Add(newData);
return newData;
}
public static void Delete(MyData itemToRemove)
{
allMyDatas.Remove(itemToRemove);
}
}
Everywhere you use a MyData you'll need to Delete it when you're finished with it.
Your code becomes
var thisTest = MyData.Create();
thisTest.Device = "blah";
thisTest.Number = "blah2";
var thisTest2 = MyData.Create();
thisTest2.Device = "blah5";
thisTest2.Number = "blah6";
//Another Part in my code.
//Need to find the MyData Structure that 'device' variable = the string 'blah'
var Foundit = MyData.AllInstances.FirstOrDefault(md => md.Device == "blah");
if(Foundit != null)
Foundit.Revision = "blah9999";
Changing FoundIt now also changes thisTest
P.S.: It's important that nothing outside MyData can new an instance of MyData. If it could, then there would be an instance of MyData that you couldn't find in AllInstances. Declaring the constructor private means a compiler error will be generated if code outside MyData tries something like var someData = new MyData
To be able to find instances of an object created earlier, these instances need to be saved somewhere.
One solution would be to put them into a list and later search that list:
var list = new List<MyTest>();
MyTest thisTest=new MyTest();
thisTest.device=blah;
thisTest.number=blah2;
list.Add(thisTest);
MyTest thisTest2=new MyTest();
thisTest2.device=blah5;
thisTest2.number=blah6;
list.Add(thisTest2);
Now you can search using LINQ:
var foundItems = list.Where(x => x.device == "blah");
foreach(var foundItem in foundItems)
{
foundItem.revision = "blah9999";
}
Please note:
This only works when you use classes instead of structs as Binary Worrier points out in his comment.
In this case a class would work better because of the dynamic string size and the fact that there are so many strings.
With your test code, you should be storing a List<MyTest> somewhere in that class and adding thisTest and thisTest2 to the list. You can later retrieve specific values (or all the values of a certain device) with the FindAll or similar methods.
List<MyTest> list = new List<MyTest>();
//add MyTests here...
var foundIt = list.FindAll(x => x.device == "blah");
You can use lists and Linq for that.
var test = new List<MyTest>();
//Add some items
var foundIt = test.SingleOrDefault(test => test.device == "abc");//Maximum one
if(foundIt != null)//Use a class for MyTest.
foundIt.device = "123"

Categories