I'm trying to get some values from a PLC (each value has a name and a int value) and save them for backup reasons on a PC.
So i got a Struct for the name and the int
public struct sValues
{
public string ObjectName;
public int Value;
}
As there are many values that I need to store I also got a class with all of them.
public class MemMainS
{
public sValues svMode;
public sValues svFreqOrBal;
(...)
}
And a List of the Class
MemMainS mainCurrent = new MemMainS();
public List<MemMainS> TestList = new List<MemMainS>();
I also got some test values
private void SetTest()
{
mainCurrent.svMode.ObjectName = "Obj1.Addr1";
mainCurrent.svMode.Value = 1;
mainCurrent.svFreqOrBal.ObjectName = "Obj2.Addr2";
mainCurrent.svFreqOrBal.Value = 2;
}
When I try to get the data from the List with a foreach it's only possible if I tell the exact element of the List
foreach (var mv in TestList)
{
FieldInfo[] listFields = mv.GetType().GetFields(BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance);
int j = 0;
foreach (FieldInfo lf in listFields)
{
FieldInfo[] classFields = lf.FieldType.GetFields(BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance);
foreach(FieldInfo cf in classFields)
{
sValues sv;
//sv = TestList.ElementAt(j);//Possible to get it working like this?
sv = TestList.ElementAt(j).svMode;//Works for just svMode
//Output:
//Obj1.Addr1
//1
}
j++;
}
}
Am I missing something that I didn't thought of yet? Or is it even possible?
If I try sv = TestList.ElemntAt(j); VS compiler tells me that the type is not the same but the type is of sValues as sv is.
Cannot implicitly convert type 'WinFormServer.Memory.MemMainS' to
'WinFormServer.Memory.sValues'
I suggest taking advantage of the indexer properties in .Net to modify the MemMainS class to behave just a little like a Dictionary collection, like this:
public class MemMainS
{
private Dictionary<string, sValues> data = new Dictionary<string, sValues>();
public ICollection<string> Keys {get {return data.Keys;} }
public Dictionary<string, sValues>.ValueCollection.Enumerator GetEnumerator()
{
return data.Values.GetEnumerator();
}
public sValues this[string valueName]
{
get
{
if (data.ContainsKey(valueName)) return data[valueName];
return default(sValues); // could opt to throw an exception here instead
}
set
{
data[valueName] = value;
}
}
public sValues svMode {get {return data["svMode"]; } set {data["svMode"] = value;} }
public sValues svFreqOrBal {get {return data["svFreqOrBal "]; } set {data["svFreqOrBal "] = value;} };
// (...)
}
That will let you re-write the loop like this to avoid reflection:
foreach (var mv in TestList)
{
foreach(string item in mv.Keys)
{
sValues sv = mv[item];
}
}
Or like this:
foreach(var mv in TestList)
{
foreach(sValues sv in mv)
{
//...
}
}
The problem here is we don't know which sValue property we're looking at in those loops. We have the ObjectName address, but not the property name. It seems like those property names should be included in the struct data. You have a property name, like svFreqOrBal, that will have a 1:1 mapping to a ObjectName or address like Obj2.Addr2. There's no reason not to include that as part of the struct.
While I'm here, I suggest defining the struct using a simple immutable pattern this way:
public struct sValues
{
public string ObjectName {get;private set;}
public int Value {get; private set;}
public sValues(string objectName, int Value)
{
ObjectName = objectName;
this.Value = Value;
}
}
(Of course, with the potential addition of the name property as suggested above).
Finally, seeing in the comments you have more than 1000 potential different PLC values, you may want to forgo the named properties in MemMainS entirely, and only use the Dictionary + indexer. Of course, this costs you some compile-time safety, but you can make up for some of this by getting a list or string[] of your valid PLC value names you can check against in the indexer for validation.
You list a List<MemMainS>; therefore the j'th element of the list is a MemMainS. You are trying to assign it to sv, a sValues value. That doesn't work by default; you can add an implicit conversion operator, but you really probably shouldn't. But if you search "C# overload implicit conversion operator" it'll show you how. It is much easier to just access the .svMode member of the j'th element.
Related
I am wondering if something like this is possible. I am looking to create a method that can be called instead of List.Add. The method would check any/all string properties and make sure they don't exceed their specific given max lengths, and if so truncate to proper size. I would ideally like it to be generic so that it will not only work for ObjectA, but also ObjectB, ObjectC, etc.
I am open to any and all suggestions. I know it seems like a weird thing to do, but I have a lot of different objects I am working with and potentially millions of those object instances in totality across all my lists. I mainly just need a way to ensure that any objects with properties exceeding their max string limit are truncated and logged via the Worker class in a timely way. Thanks!
public class ObjectA {
public Guid aID {get; set;}
[MaxLength(128)]
public string aName {get; set;}
[MaxLegnth(30)]
public string aType {get; set;}
}
--
public class Worker {
private void Work() {
List<ObjectA> listOfA = new List<ObjectA>();
listOfA.CustomAddMethod(new ObjectA(new Guid, "Something", "Unknown"));
}
// ??????
private CustomAddMethod(T object) {
foreach property {
if (isStringProperty && isGreaterThanMaxLength) {
// truncate to proper size
// log for truncation message
}
// Then add to list
}
}
}
You can create an extension method.
Here is a code snip. You can improve the performance by implementing a cache, for example, using a dictionary to store the properties and the MaxLengthAttribute based on object's type.
public static class ListExtensions
{
public static void CustomAdd<T>(this List<T> list, T item, Action<string> logger = null)
{
var propertyInfos = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(propertyInfo => propertyInfo.PropertyType == typeof(string))
.ToList();
foreach (var propInfo in propertyInfos)
{
var maxLengthAttr = propInfo
.GetCustomAttributes(typeof(MaxLengthAttribute))
.Cast<MaxLengthAttribute>()
.FirstOrDefault();
if (maxLengthAttr is null)
continue;
var currentString = (string)propInfo.GetValue(item);
if (!maxLengthAttr.IsValid(currentString))
{
var newValue = currentString.Substring(0, maxLengthAttr.Length);
logger?.Invoke(
$"Resolving error: {maxLengthAttr.FormatErrorMessage(propInfo.Name)}\n" +
$"Old Value: {currentString}\n" +
$"New Value: {newValue}"
);
propInfo.SetValue(item, newValue);
}
}
list.Add(item);
}
}
Example of usage (code removed for brevity):
public class Person
{
[MaxLength(4)]
public string Name { get; set; }
}
...
var personList = new List<Person>();
personList.CustomAdd(
new Person {Name = "John Doe"},
message => Debug.WriteLine(message)
);
...
As result the Jhon Doe string will be trimmed to Jhon
I am facing an issue, surely due to my lack of knowledge in the reflection process, while trying to set a "complex" class hierarchy based on Json files.
Here are my main model :
public class Names
{
public Weapons Weapons { get; set; }
public Armors Armors { get; set; }
public Utilities Utilities { get; set; }
public Names()
{
Weapons = new Weapons();
Armors = new Armors();
Utilities = new Utilities();
}
}
Each of them having a list of sub-model like this:
public class Weapons
{
public BattleAxe BattleAxe { get; set; } = new BattleAxe();
public Bomb_Missile Bomb_Missile { get; set; } = new Bomb_Missile();
// etc... Around 20 to 25
}
And finally the ended model which is the exact equivalent of each json files but may have very different properties :
public class BattleAxe
{
public string[] Normal { get; set; } = new string[0];
public string[] DescriptiveAdjective { get; set; } = new string[0];
public string[] Material { get; set; } = new string[0];
public string[] Type { get; set; } = new string[0];
public string[] Title { get; set; } = new string[0];
public string[] Of { get; set; } = new string[0];
public string[] NormalForTitle { get; set; } = new string[0];
}
Since the MS Json deserializer does not support the conversion to a $type as Newtonsoft before, I tried to populate the values using reflection too like this (I've removed all the null-check for code readability) :
public static void Load()
{
Names = new Names();
foreach (var category in Names.GetType().GetProperties())
{
if (category is not null && !(category.GetGetMethod()?.IsStatic ?? false))
{
var categoryType = category.PropertyType;
foreach (var item in category.PropertyType.GetProperties())
{
var itemType = item.PropertyType;
var subTypeData = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(File.ReadAllText($"./Assets/Names/{categoryType.Name}/{itemType.Name}.json"));
var concreteObj = Activator.CreateInstance(itemType);
foreach (var key in subTypeData.Keys)
{
if (itemType.GetProperty(key) is not null && concreteObj is not null)
{
var prop = concreteObj.GetType().GetProperty(key);
var convertedValue = ConvertJsonType(subTypeData[key], subTypeData[key].ValueKind, out var isReferenceType);
// It fails here
prop.SetValue(
isReferenceType ? convertedValue : null,
!isReferenceType ? convertedValue : null
);
}
}
item.SetValue(concreteObj, null);
}
}
}
}
So it fails at the prop.SetValue(...) of the deepest object in the hierarchy with a different error depending on the type of value to set.
If it is a reference, it throws a System.Reflection.TargetException : 'Object does not match target type' Exception
And if it is value, it throw a System.Reflection.TargetException : 'Non-static method requires a target.'
Knowing that I do not have problems around the deserialization as shown here, only the fact that I use a dynamic type (and my instinct tells me it is actually the problem...)
I do not add the ConvertJsonType(...) body as it is functional and really simple
I am more interested in the 'why' than the 'how' so if you can explain me the 'theory' behind the problem, that would help quite a lot :)
Thank you!
PS: I know I can simplify the things in a more readable/performant way but I must achieve it with reflection for personal learning :)
Same for the System.Text.Json namespace, I do not intend to switch back to Newtonsoft for that
When calling SetValue(instance, value) you should pass the object which property should be set.
It's a wild guess, but you could try this:
prop.SetValue(concreteObj,
!isReferenceType ? convertedValue : null);
Because you want to fill the properties of concreteObj, not the value it self.
If you look at the object prop it was a return value of concreteObj.GetType().GetProperty(key);. If you look at it close, The GetProperty is a method from Type which isn't bound to any instance. So that's why you need to pass the instance of the object as the first parameter.
I mean this in a positive way: The itemType.GetProperty(key) is called every iteration, it will be the same value each iteration, you could bring it before the loop.
As docs state TargetException is thrown when:
The type of obj does not match the target type, or a property is an instance property but obj is null.
Passing null for obj in SetValue is valid when you are trying to set value for static property, not an instance one. Property type being a reference one has nothing to do with property being instance or static one so your call should look something like:
prop.SetValue(concreteObj, convertedValue);
Also your item.SetValue(concreteObj, null); does not look right cause concreteObj should be second argument in this call. Something like this:
item.SetValue(Names, concreteObj);
Also if you want only instance properties you can provide BindingFlags to get only instance properties:
foreach (var category in Names.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
Also I would say that category is not null check is redundant so in pair with providing BindingFlags you should remove the if completely.
I have a class that contains multiple string fields. Whenever an object of this class is instantiated, I'd like those fields to be automatically assigned with the same specific default value (something like "Undefined"). The reason is:
If I have to serialize the object before all fields are populated with real data, I want those fields to display as this default value rather than being null or string.Empty.
String fields may be added/removed from this class as the project progresses. I'd like to not have to touch the constructor every time that occurs.
Is there any way to do this other than explicitly assigning the default value to each of the string fields one by one in the class constructor?
In C# 6.0 and above, you can use Auto-Property Initializer:
https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-6#auto-property-initializers
Basically:
public string Property { get; set; } = "UNDEFINED";
You would have to use reflection. Something like this
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.PropertyType == typeof(string)) property.setValue(obj, "UNDEFINED");
}
First of all: I don't see how it could be best practice to do what you want.
If you want something like this to show up in your code:
public string Property { get; set; } = "UNDEFINED";
You should probably look into creating custom snippets that simply write exactly that. e.g. https://msdn.microsoft.com/en-us/library/ms165394.aspx
If you don't want that, you could use reflection to find all fields (e.g. strings) in the constructor and set them.
C# Reflection - Get field values from a simple class
FieldInfo[] fields = data.GetType().GetFields(BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);
Setting a property by reflection with a string value
Ship ship = new Ship();
string value = "5.5";
PropertyInfo propertyInfo = ship.GetType().GetProperty("Latitude");
propertyInfo.SetValue(ship, Convert.ChangeType(value, propertyInfo.PropertyType), null);
Well, why not have an extension method like
public static class MyClass
{
public static string GetDefault(this str, string defaultVal)
{
return string.IsNullOrEmpty(str) ? defaultVal : str;
}
}
For a type
public class SomeClass
{
public string str = string.Empty;
}
You can call
SomeClass s = new SomeClass();
s.str.GetDefault("UNDEFINED");
You can initialize values to fields directly instead of in the constructor.
private string myStringVariable = "UNDEFINED";
Perhaps you should reconsider the structure of your program though if it permits many fields to be initialized to undefined.
Maybe I am misunderstanding this but why not do word for word what you described in the question in your constructor?
public class Weee
{
public string name { get; set; }
public int order { get; set; }
public string whatever { get; set; }
public Weee()
{
foreach(var p in typeof(Weee).GetProperties().Where(a => a.PropertyType == typeof(string)))
{
p.SetValue(this, "wut");
}
}
}
You can create a property initializer and have a base class use it. Your classes can then inherit from the base and have their properties automatically initialized:
public class PropertyInitializer
{
public void Initialize<T>(object obj, T value)
{
PropertyInfo[] properties = obj.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.PropertyType == typeof(T))
{
property.SetValue(obj, value);
}
}
}
}
public class InitializedBase
{
protected InitializedBase()
{
var initializer = new PropertyInitializer();
//Initialize all strings
initializer.Initialize<string>(this, "Juan");
//Initialize all integers
initializer.Initialize<int>(this, 31);
}
}
//Sample class to illustrate
public class AutoInitializedClass : InitializedBase
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return string.Format("My name is {0} and I am {1} years old", Name, Age);
}
}
Sample usage:
AutoInitializedClass sample = new AutoInitializedClass();
Console.WriteLine(sample);
Console output:
My name is Juan and I am 31 years old
Notice the base class is using the PropertyInitializer class to initialize fields. This is a simplified example. You can expand it as it fits you (it may not work out of the box with all types).
I personally don't recommend this. It's called a constructor for a reason but you asked a question and I provided an answer.
Here is a simple class from which you can inherit that does exactly what you want:
Example usage:
public class MyClass : DefaultedObject<string>
{
public string MyStringField;
protected override string Default => "UNDEFINED";
}
var myClass = new MyClass();
// myClass.MyStringField == "UNDEFINED"
Implementation:
public abstract class DefaultedObject<T>
{
protected DefaultedObject()
{
T defaultValue = Default;
FieldInfo[] fields = GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach(FieldInfo field in fields) {
if(field.FieldType == typeof(T)) {
field.SetValue(this, defaultValue);
}
}
}
protected abstract T Default { get; }
}
I appreciate all the feedback to this question. Here's what ended up working. First, for any string attributes in the class that I wanted to receive an automatic default value, I established as a property:
public string attribute1 {get; set;}
public string attribute2 {get; set;}
And so on. Then, in the class constructor, I included the following loop which iterates through each property of type string:
foreach(PropertyInfo property in GetType().GetProperties())
{
if (property.PropertyType == typeof(string))
property.SetValue(this, "UNDEFINED"));
}
This produced the desired outcome for me.
I have two classes with property getters only
public class A
{
public A(string name)
{
Name = name;
}
public string Name { get; }
public string Value { get; set;}
public string Data { get; set;}
}
public class B
{
public B(string name)
{
Name = name;
}
public string Name { get; }
public string Value { get; set;}
}
They are different in shape but share some of the same property names and types.
How can I copy values when they only have getters?
This is a typical scenario when I send an object as a constructor parameter to extract values from in the new object. Then I need to copy values one by one. This can produce lots of code and is hard to maintain.
Can this be made simpler? Is there a way to use reflection to copy objects when the target only has getter properties?
It is possible to create a helper function that copies two objects where the target object only has property getters.
Try this;
public static void CopyItem<U, T>(U source, T target)
{
// Need a way to rename the backing-field name to the property Name ("<A>k__BackingField" => "A")
Func<string, string> renameBackingField = key => new string(key.Skip(1).Take(key.IndexOf('>') - 1).ToArray());
// Get public source properties (change BindingFlags if you need to copy private memebers as well)
var sourceProperties = source.GetType().GetProperties().ToDictionary(item => item.Name);
// Get "missing" property setter's backing field
var targetFields = typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetField).ToDictionary(item => renameBackingField(item.Name));
// Copy properties where target name matches the source property name
foreach(var sourceProperty in sourceProperties)
{
if (targetFields.ContainsKey(sourceProperty.Key) == false)
continue; // No match. skip
var sourceValue = sourceProperty.Value.GetValue(source);
targetFields[sourceProperty.Key].SetValue(target, sourceValue);
}
}
This is a generic description. You will probably want to check the property and field data types as well before you copy the value to prevent exceptions. Both name and data type should match.
Use CopyItem to copy matching source properties in a constructor;
public class SomeClass
{
public SomeClass(SomeSourceClass source)
{
Helper.CopyItem(source, this);
}
}
This seems like the most basic thing ever but somehow I couldnt find the answer and couldnt figure it out.
Lets say I have a custom class:
public class WineCellar
{
public string year;
public string wine;
public double nrbottles;
}
Now I would like a function:
WineCellar ex = new WineCellar();
ex.members();
This should return: year, wine, nrbootles.
And:
ex.members().types();
Should return: string, string, double
I guess on the same note, lets say you have one instance {2010, Rioja, 6}. Is there syntax that returns these by indexing? i.e.
ex[1]
or
ex.{1}
that returns 2010?
Sorry for the basic question.
As Michelle said in the comments, this sounds like a wrong approach to a bigger problem.
However, if you do need this kind of things, you can get the using reflection:
//returns a list of propertyInfo objects for the class
// with all kinds of usefull information
public List<PropertyInfo> GetMemberInfos()
{
return this.GetType().GetProperties().ToList();
}
//returns a list of property names
public List<string> GetMemberNames
{
return this.GetType().GetProperties().Select(pi => pi.Name).ToList();
}
//returns a list of names of the property types
public List<string> GetMemberTypeNames
{
return this.GetType().GetProperties().Select(pi => pi.PropertyType.Name).ToList();
}
//indexer that uses the property name to get the value
//since you are mixing types, you can't get more specific than object
public object this[string property]
{
get { return this.GetType().GetProperty(property).GetValue(this); }
set { this.GetType().GetProperty(property).SetValue(this, value); }
}
//indexer that uses the property index in the properties array to get the value
public object this[int index]
{
get { return this.GetType().GetProperties()[index].GetValue(this); }
set { this.GetType().GetProperties()[index].SetValue(this, value); }
}
Note that all of these methods are very slow, because in general, reflection is slow. You can try to cache some thing to speed it up.
Also, the last method is downright dangerous. It will (try to) read and write to an array that does not have a guaranteed order. In fact, the documentation specifies:
The GetProperties method does not return properties in a particular
order, such as alphabetical or declaration order. Your code must not
depend on the order in which properties are returned, because that
order varies.
For example, if you change your class to:
public class WineCellar
{
public string year;
public string region;
public string wine;
public double nrbottles;
}
and you were used to using winecellar[1] = "Pinot Noir" that will most likely now update the region property, instead of the wine property.
This is how you would implement Members method (In case if you wanted property names as strings)
public List<string> Members()
{
List<string> propNames = new List<string>();
foreach (var prop in typeof(WineCellar).GetProperties())
{
propNames.Add(prop.Name);
}
return propNames;
}
And this is how you would implement Types (In same case)
public List<string> Types()
{
List<string> propTypes = new List<string>();
foreach (var prop in typeof(WineCellar).GetProperties())
{
propTypes.Add(prop.PropertyType.ToString());
}
return propTypes ;
}
And the last thing if you want to get values of the parameters like this ex[n] you can just make a simple indexer in you class like this
public string this[int n]
{
get
{
int current = 0;
foreach (var prop in typeof(WineCellar).GetProperties())
{
if (current == n)
return prop.GetValue(this, null).ToString();
current++;
}
return null;
}
}
but for these methods to work you should change your variables into properties like this
public class WineCellar
{
public string Year { get; set; }
public string Wine { get; set; }
public double Nrbottles { get; set; }
}
You can use reflection
foreach (var prop in typeof(WineCellar).GetProperties())
{
if (prop.PropertyType == typeof(double) || prop.PropertyType == typeof(double?))
{
}
}
to get the value, you can do:
prop.GetValue(obj);