I have the following class properties:
[EffectAspect(Enums.Effects.Low)]
public int Wind { set; get; }
[EffectAspect(Enums.Effects.Low)]
public int Fire { set; get; }
[EffectAspect(Enums.Effects.Medium)]
public int Water { get; set; }
[EffectAspect(Enums.Effects.Huge)]
public int Earth { get; set; }`
Now, let's say I want to calculate the total Lows, Mediums and Huges.
So I wrote something like:
List<Enums.Effects> result = new List<Enums.Effects>();
PropertyInfo[] properties = GetType().GetProperties();
foreach (PropertyInfo p in properties)
{
object[] attrs = p.GetCustomAttributes(true);
foreach (Object attr in attrs)
{
var effectAttr = attr as EffectAspect;
if (effectAttr != null)
{
int amount = (int)p.GetConstantValue();
for (int i = 0; i < amount; i++)
{
result.Add(effectAttr.Aspect);
}
}
}
}
return result;
For example: if Wind = 3, there would be at least 3 Enums.Effects.Low inside the result list.
[AttributeUsage(AttributeTargets.Property)]
public sealed class EffectAspectAttribute : Attribute
{
public Enums.EffectsAspect { get; private set; }
public EffectAspectAttribute (Enums.EffectsAspect aspect)
{
this.Aspect = aspect;
}
}
The problems is: int amount = (int)p.GetConstantValue(); throws exceptions says:
Literal value was not found.
And I couldn't find what it means.
You can try to use
p.GetValue(this, null)
instead of
p.GetConstantValue();
You can refer this link thread: Difference between GetValue, GetConstantValue and GetRawConstantValue
Related
I'm creating a component for Blazor and facing an issue when I have to create at runtime an object and I want to copy most of the properties, basically to clone an object but without same properties.
For example, the class Checkbox has a property called Choices.
public class Checkbox : ElementBase, IElement {
public virtual List<object>? Choices { get; set; }
}
This property stores string or CheckboxChoice.
public class CheckboxChoice
{
public string? Label { get; set; }
public string? VisibleIf { get; set; }
}
So, at runtime I have to create a new instance of an object (for example Checkbox) to display the component I want. To create an instance, I use this code (el is an implementation of IElement)
var newInstance = el.GetType();
var instance = Activator.CreateInstance(newInstance) as IElement;
Now, I have to copy some of the property's values from el to the instance. In order to copy all the properties I want, I use this extension
public static class ElementExtensions
{
public static T CopyTo<T, S>(this T target, S source, string[] propertyNames)
{
if (source == null)
return target;
Type sourceType = typeof(S);
Type targetType = typeof(T);
BindingFlags flags = BindingFlags.IgnoreCase | BindingFlags.Public |
BindingFlags.Instance;
PropertyInfo[] properties = sourceType.GetProperties();
foreach (PropertyInfo sPI in properties)
{
if (!propertyNames.Contains(sPI.Name))
{
PropertyInfo tPI = targetType.GetProperty(sPI.Name, flags);
if (tPI != null && tPI.CanWrite &&
tPI.PropertyType.IsAssignableFrom(sPI.PropertyType))
{
tPI.SetValue(target, sPI.GetValue(source, null), null);
}
}
}
return target;
}
}
The problem with this extension is that it is not copying the property Choices or other property that are not a primitive type.
I don't want to use AutoMapper because I like to have a light component without dependencies, apart from .NET6. How can I change the function to copy also the complex properties?
Update
This is the code I use. ElementData has the list of components (like Checkbox and Textbox) to display.
public List<IElement>? RepeaterElements { get; set; }
foreach (var el in ElementData)
{
var newInstance = el.GetType();
var instance = Activator.CreateInstance(newElement, null);
instance.CopyTo(data, new[] { "Parent", "Index", "QuestionNumber", "Name" });
instance.Parent = parentName;
instance.Index = row;
instance.QuestionNumber = question;
instance.Name = instance.GetElementName();
RepeaterElements.Add(new RepeaterElement() { Element = instance, Row = row });
}
I've always defaulted to using serialization to handle deep object copies.
Here is an example:
using static Newtonsoft.Json.JsonConvert;
var instanceOfMyClass = new MyClass()
{
Id = 1,
Name = "John Doe",
Age = 21
};
Console.WriteLine(SerializeObject(instanceOfMyClass));
var copyOfMyClass = DeserializeObject<MyClass>(SerializeObject(new { instanceOfMyClass.Name, instanceOfMyClass.Age }));
Console.WriteLine(SerializeObject(copyOfMyClass));
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
It's a simple and performant pattern on small classes.
EDIT:
Can also be done between classes if the Property names are the same:
sing static Newtonsoft.Json.JsonConvert;
var instanceOfMyClass = new MyClass()
{
Id = 1,
Name = "John Doe",
Age = 21
};
Console.WriteLine(SerializeObject(instanceOfMyClass));
var copyOfMyClass = DeserializeObject<MySecondClass>(SerializeObject(new { instanceOfMyClass.Name, instanceOfMyClass.Age }));
Console.WriteLine(SerializeObject(copyOfMyClass));
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class MySecondClass
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
I want to compare all of my list of generic T property value to my local variable searchText.
i already try:
x.GetType().GetProperties().All(props => props.GetValue(x).ToString().ToLower().Contains(searchText.ToLower()))
and i get error : NullReferenceException: Object reference not set to an instance of an object.
here my full method :
protected List<T> ProcessCollection<T>(List<T> lstElements, IFormCollection requestFormData, Func<T, IComparable> getProp)
{
string searchText = string.Empty;
Microsoft.Extensions.Primitives.StringValues tempOrder = new[] { "" };
if (requestFormData.TryGetValue("search[value]", out tempOrder))
{
searchText = requestFormData["search[value]"].ToString();
}
var skip = Convert.ToInt32(requestFormData["start"].ToString());
var pageSize = Convert.ToInt32(requestFormData["length"].ToString());
if (requestFormData.TryGetValue("order[0][column]", out tempOrder))
{
var columnIndex = requestFormData["order[0][column]"].ToString();
var sortDirection = requestFormData["order[0][dir]"].ToString();
tempOrder = new[] { "" };
if (requestFormData.TryGetValue($"columns[{columnIndex}][data]", out tempOrder))
{
var columName = requestFormData[$"columns[{columnIndex}][data]"].ToString();
if (pageSize > 0)
{
var prop = GetProperty<T>(columName);
if (sortDirection == "asc")
{
return lstElements.Where(x=>x.GetType().GetProperties().All(props => props.GetValue(x).ToString().ToLower().Contains(searchText.ToLower()))
.Skip(skip).Take(pageSize).OrderBy(b => prop.GetValue(b)).ToList();
}
else
return lstElements
.Where(x => getProp(x).ToString().ToLower().Contains(searchText.ToLower()))
.Skip(skip).Take(pageSize).OrderByDescending(b => prop.GetValue(b)).ToList();
}
else
return lstElements;
}
}
return null;
}
this is how i call my method :
var listItem = ProcessCollection<Jtabel>(temp,requestFormData,x=>x.Name);
the temp that i pass to method already filled
and this is the class type that i pass to this method
public class Jtabel : BaseModel
{
public string Creator { get; set; }
public string Name { get; set; }
public int SO { get; set; }
public int SOD { get; set; }
public int SAP { get; set; }
public int BA { get; set; }
public int Invoice { get; set; }
public int EPD { get; set; }
public double DP { get; set; }
}
i will try to explain more detail as much i can, so i want to get all of my property from Jtabel class to my ProcessCollection where inside the method all of Jtable property will test each element that's contain searchText.
I am trying to implement object deep/shallow cloning service using Reflection.
Using the function Clone<T> Simple class is being copied with all the required fields, but in case of SimpleStruct Computed field does not get copied.
What is the difference between struct and class when defining read-only fields, and how this can be solved ?
Thanks in advance.
public T Clone<T>(T source)
{
var obj = Activator.CreateInstance<T>();
var type = source.GetType();
foreach (var property in type.GetProperties())
{
if (!property.IsValid())
continue;
if (property.SetMethod != null)
{
property.SetValue(obj, property.GetValue(source));
}
}
foreach (var field in type.GetFields())
{
if (field.IsPublic == false || !field.IsValid())
continue;
field.SetValue(obj, field.GetValue(source));
}
return obj;
}
public struct SimpleStruct
{
public int I;
public string S { get; set; }
[Cloneable(CloningMode.Ignore)]
public string Ignored { get; set; }
public string Computed => S + I;
public SimpleStruct(int i, string s)
{
I = i;
S = s;
Ignored = null;
}
}
public class Simple
{
public int I;
public string S { get; set; }
[Cloneable(CloningMode.Ignore)]
public string Ignored { get; set; }
[Cloneable(CloningMode.Shallow)]
public object Shallow { get; set; }
public string Computed => S + I + Shallow;
}
I want to create some classes, that have to look a certain way ( the way shown in my example).
Some of the class properties are classes (or structs) themselves.
I want to write a method within my classes that get the Property-Values of all the Properties, that are Structs and write them to a string.
So this is what my classes look like:
public class car
{
public string brand { get; set; }
public tire _id { get; set; }
public string GetAttributes()
{
Type type = this.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach(PropertyInfo propertyInfo in properties)
if (propertyInfo.PropertyType.ToString().Contains("_"))
{
//I want to write the actual value of the property here!
string nested_property_value = ...
return nested_property_value;
}
}
}
this is what my structs look like:
public struct tire
{
public int id { get; set; }
}
this would be the Main Program:
tire mynewtire = new tire()
{
id = 5
};
car mynewcar = new car()
{
_id = mynewtire
};
Anyone has an idea how to create the GetAttributes-Methode? I've been trying to figure this out for ages now, but don't get there...
This code will get you started. I recommend you look at other serialisation methods (such as JSON) as well.
using System;
namespace Test
{
public class car
{
public string brand { get; set; }
public tire _id { get; set; }
public string GetAttributes()
{
var type = this.GetType();
var returnValue = "";
var properties = type.GetProperties();
foreach (var propertyInfo in properties)
{
// Look at properties of the car
if (propertyInfo.Name.Contains("_") && propertyInfo.PropertyType.IsValueType &&
!propertyInfo.PropertyType.IsPrimitive)
{
var propValue = propertyInfo.GetValue(this);
var propType = propValue.GetType();
var propProperties = propType.GetProperties();
foreach (var propPropertyInfo in propProperties)
{
// Now get the properties of tire
// Here I just concatenate to a string - you can tweak this
returnValue += propPropertyInfo.GetValue(propValue).ToString();
}
}
}
return returnValue;
}
}
public struct tire
{
public int id { get; set; }
}
public class Program
{
static void Main(string[] args)
{
var mynewtire = new tire()
{
id = 5
};
var mynewcar = new car()
{
_id = mynewtire
};
Console.WriteLine(mynewcar.GetAttributes());
Console.ReadLine();
}
}
}
I have a model with a bunch of fields defined like this:
public class Transaction
{
public DateTime R03DateFrom { get; set; }
public DateTime? R03DateTo { get; set; }
[MaxLength(80)]
public string R03Place { get; set; }
[MaxLength(20)]
public string R03Code { get; set; }
// And so on....
}
At a certain point I need to export some of this data to fixed width files, and if a string has a MaxLength of x, then in the output file it should always be output right-padded with spaces up to x characters.
I'm hoping to re-use the fact that the string's MaxLength is always defined in the Model in order to flow this information through to the export.
At the moment, a typical export row function looks like this (ExDateTime is an extension method that formats the date in yyyyMMddhhmm format):
private string GetR03Row()
{
return GetRowCode() + "03" +
R03DateFrom.ExDateTime() +
R03DateTo.ExDateTime() +
(R03Place??"").PadRight(80) +
(R03Code??"").PadRight(20);
}
I'd like to replace the line
(R03Place??"").PadRight(80)
with something that uses the MaxLength attribute.
Every string will have a MaxLength.
UPDATE:
I've taken the suggestion below and turned it into an Extension Method:
public static string PadToMax<T>(this string source, string propName)
{
PropertyInfo[] props = typeof(T).GetProperties();
var found = props.Where(m => m.Name.Equals(propName));
if (!found.Any()) return source;
var propertyInfo = found.First();
var attrs = propertyInfo.GetCustomAttributes(false);
if (!attrs.Any()) return source;
foreach (var maxLengthAttribute in attrs.OfType<MaxLengthAttribute>())
{
return (source??"").PadRight(maxLengthAttribute.Length);
}
return source;
}
which allows me to use this syntax to achieve what I want:
// (R03Place??"").PadRight(80) turns into
R03Place.PadToMax<Transaction>(nameof(R03Place))
This is fine. I'd love it if I could change the extension method to somehow work out the "Transaction" type and the name of the source string variable. But thi is close enough.
What Olivier proposed should work.
Here another way:
private static void Main(string[] args)
{
var maxLength = GetMaxLengthAttributeValue<Transaction>("R03Place");
Console.WriteLine("R03Place = {0}",maxLength);
maxLength = GetMaxLengthAttributeValue<Transaction>("R03Code");
Console.WriteLine("R03Place = {0}",maxLength);
Console.ReadLine();
}
public static int? GetMaxLengthAttributeValue<T>(string propertyName)
{
PropertyInfo[] props = typeof (T).GetProperties();
var found = props.Where(m => m.Name.Equals(propertyName));
if (!found.Any()) return null;
var propertyInfo = found.First();
var attrs = propertyInfo.GetCustomAttributes(false);
if (!attrs.Any()) return null;
foreach (object attr in attrs)
{
MaxLengthAttribute maxLengthAttribute = attr as MaxLengthAttribute;
if (maxLengthAttribute != null)
{
return maxLengthAttribute.Length;
}
}
return null;
}
Put the method in a helper class:
//You can use it as:
(R03Place??"").PadRight(YourHelper.GetMaxLengthAttributeValue<Transaction>("R03Place").Value);
// with C# 6, you don't have to hard code the property name
(R03Place??"").PadRight(YourHelper.GetMaxLengthAttributeValue<Transaction>(nameof(R03Place)).Value);
Could you save the maxlength as a private field?
public class Transaction
{
private int maxLengthPlace = 80
public DateTime R03DateFrom { get; set; }
public DateTime? R03DateTo { get; set; }
[MaxLength(maxLengthPlace)]
public string R03Place { get; set; }
[MaxLength(20)]
public string R03Code { get; set; }
// And so on....
}
and then use the same field later on?
(R03Place??"").PadRight(maxLengthPlace)