Related
I want to create a dictionary that has all of the public properties of an object mapped to the plain string property name. For example, the following "Person" class should return the following dictionary. I think I want to walk the object tree with the .GetProperties() method, but I am having trouble with where to go from there.
Code:
public class Person
{
public string FirstName;
public string LastName;
public Address Addr;
}
public class Address
{
public string Country;
public City City;
}
public class City
{
public string ZipCode;
public string Street;
}
Dictionary Result:
Property Name
Full Path
FirstName
FirstName
LastName
LastName
Country
Addr.Country
ZipCode
Addr.City.ZipCode
Street
Addr.City.Street
Although I couldn't figure out why you need to do it, you can use reflection to get all data you need:
void PropertyToDictionary()
{
Person person = new()
{
FirstName = "Johnny",
LastName = "Depp",
Addr = new()
{
City = new()
{
Street = "Street",
ZipCode = "12345"
}
}
};
var dic =new Dictionary<string, string>();
person.GetType()
.GetProperties()
.ToList()
.ForEach(p =>
{
if (p.Name == "Addr")
{
p.GetValue(person)
.GetType()
.GetProperties()
.ToList()
.ForEach(pp =>
{
if (pp.Name == "City")
{
pp.GetValue(person.Addr)
.GetType()
.GetProperties()
.ToList()
.ForEach(ppc =>
{
dic.Add(ppc.Name, $"{p.Name}.{pp.Name}.{ppc.Name}");
});
}
else
{
dic.Add(pp.Name, $"{p.Name}.{pp.Name}");
}
});
}
else
{
dic.Add(p.Name,p.Name);
}
});
foreach (var item in dic)
{
Console.WriteLine(item.Key + "-----" + item.Value);
}
}
Output:
Apologies that I did not do a good job explaining my problem. I wanted to keep it high level from the actual core of the problem, but I think that ended up being more confusing.
Essentially, I have a web project that makes requests to search, sort, etc. on Database Entities. The requests only contain a single string as the property that should be used and then the type of request. That property code is at the top-level or nested. So, I needed to build out a way to get the possible top-level and nested properties that I can use and map that to just the simple name. In the end, just a dynamic dictionary with all public top-level properties and nested properties.
Here is how I ended up solving it:
/// <summary>
/// This method will populate the <paramref name="propertiesDictionary"/> with keys of all of the public property and nested
/// public properties. This is useful for mapping search, sort, etc. requests with the request property to the fully pathed
/// with the object property name. The values of the dictionary can then be used in linq queries to pull the data from the entities.
/// Important notes:
/// 1. This will only use the top most level property name if there are multiple properties with the same name.
/// 2. This will not handle nested objects of the same type.
/// 3. It requires all objects to reside in the same namespace. This is needed to ignore properties on strings, Lists, etc. However, this means only objects in that namespace will be used.
/// </summary>
/// <param name="objectType">The type of the object that will be pulled from</param>
/// <param name="propertiesDictionary">The dictionary that will be populated with the plain string names as the keys and the full pathed property has the value</param>
/// <param name="objectNameSpace">The namespace of the properties</param>
/// <param name="prefix">Prefix used in the recursive call to fill out the full path. THIS SHOULD BE EMPTY ON THE FIRST CALL</param>
/// <param name="topLevelObject">The top level object that we are using. THIS SHOULD BE NULL ON THE FIRST CALL</param>
/// <param name="recurssionIteration">The current iteration of recursive which is used to bail to avoid a stack-overflow. THIS SHOULD BE ZERO ON THE FIRST CALL</param>
private static void GetPublicPropertiesAndNestedPropertiesRecursively( Type objectType, IDictionary<string, string> propertiesDictionary, string objectNameSpace, string prefix = "", Type topLevelObject = null, int recurssionIteration = 0 )
{
// First, check to make sure that we didn't hit the recursion max
if ( recurssionIteration > 8 )
{
return;
}
// If this is not the first call, bail out if this is the same object. This would cause an infinite loop if not.
// If this is the first call, set the top level object.
if ( topLevelObject == null )
{
topLevelObject = objectType;
}
else if ( objectType == topLevelObject )
{
return;
}
// Get all the public properties and return if there is no or th
var currentProperties = objectType.GetProperties();
if ( objectType.GetProperties().Length < 1 || objectType.Namespace == null || !objectType.Namespace.StartsWith( objectNameSpace ) )
{
return;
}
// Add all of the properties that currently don't exist
foreach ( var currentProperty in currentProperties.Where( currentProperty => !propertiesDictionary.ContainsKey( currentProperty.Name.ToLower() ) ) )
{
propertiesDictionary.Add( currentProperty.Name.ToLower(), $"{prefix}{currentProperty.Name}".ToLower() );
}
// Recursively get all of the nested properties
foreach ( var propertyInfo in currentProperties )
{
GetPublicPropertiesAndNestedPropertiesRecursively( propertyInfo.PropertyType, propertiesDictionary, objectNameSpace, $"{propertyInfo.Name}.", topLevelObject, recurssionIteration++ );
}
I have a UI from where we are adding following values in a table Fields
ProductName
ProductId
ProductCode
I have a existing class Product with some existing properties
public class Product
{
public string ProductID { get; set; }
//product in a product search listing
public string StoreName { get; set; }
public string SearchToken { get; set; }
}
I am looking for a method which will add properties in existing class Product at run time (dynamically) when user adds values in a table Fields
I don't know a way of defining properties during runtime, but another possibiity for you to achieve what you need is to use a dynamic object in C# known as ExpandoObject.
You first need to declare your dynamic object, it uses a kind of Dictionary internally so you can then add your properties to it.
using System.Dynamic;
dynamic newobj = new ExpandoObject();
//I can add properties during compile time such as this
newobj.Test = "Yes";
newobj.JValue = 123;
//Or during runtime such as this (populated from two text boxes)
AddProperty(newobj, tbName.Text, tbValue.Text);
public void AddProperty(ExpandoObject expando, string propertyName, object propertyValue)
{
var exDict = expando as IDictionary<string, object>;
if (exDict.ContainsKey(propertyName))
exDict[propertyName] = propertyValue;
else
exDict.Add(propertyName, propertyValue);
}
I have used it once in a solution here: Flattern child/parent data with unknown number of columns
But these sources can probably explain it better;
https://www.oreilly.com/learning/building-c-objects-dynamically
https://weblog.west-wind.com/posts/2012/feb/08/creating-a-dynamic-extensible-c-expando-object
https://learn.microsoft.com/en-us/dotnet/articles/csharp/programming-guide/types/using-type-dynamic
But, I'm not sure that this would really offer you any real advantages over using a simple Dictionary<string, object>
As others have pointed out, it doesn't sound like your scenario calls for this behavior. However, what you're asking for is possible. Using System.Reflection.Emit, you can dynamically extend your base class at runtime and even save it at runtime as a compiled .dll. The trickier part would be to figure out how to deploy your new class to production from your production code and overwrite your previous class, if that's needed. I'll leave that part up to you to figure out. As per the rest of your question, I had a similar need when creating automated testing for a framework that requires implementers to extend base classes, so I built a mini framework to do this. You can use it like this:
public void TestTypeCreator()
{
//create and save new type
object _newProduct = DynamicTypeCreator
.Create("NewProduct", typeof(Product), #"C:\PROD")
.AddPassThroughCtors()
.AddProperty("ProductName", typeof(string))
.FinishBuildingAndSaveType("NewProduct.dll")
.GetConstructor(new Type[0] { })
.Invoke(new object[0] { });
//set ProductName value
_newProduct.GetType().GetProperty("ProductName").SetValue(_newProduct, "Cool Item");
//get ProductName value
string _prodName = _newProduct.GetType().GetProperty("ProductName").GetValue(_newProduct).ToString();
//get StoreName value
string _storeName = _newProduct.GetType().GetProperty("StoreName").GetValue(_newProduct).ToString();
}
Since I haven't published this code anywhere, I'll paste the whole thing below. This "framework" is very limited and you might have to add to it/modify it for your specific purpose. Here ya go:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.IO;
namespace ConsoleCommon
{
public interface IBaseObject
{
IEmptyObject AddPassThroughCtors();
}
public interface IEmptyObject
{
IAfterProperty AddProperty(string name, Type type);
}
public interface IAfterProperty : IEmptyObject, IFinishBuild
{
IAfterAttribute AddPropertyAttribute(Type attrType, Type[] ctorArgTypes, params object[] ctorArgs);
}
public interface IAfterAttribute : IEmptyObject, IFinishBuild
{
}
public interface IFinishBuild
{
Type FinishBuildingType();
Type FinishBuildingAndSaveType(string assemblyFileName);
}
public static class DynamicTypeCreator
{
public static IBaseObject Create(string className, Type parentType)
{
return new DynamicTypeCreatorBase().Create(className, parentType);
}
public static IBaseObject Create(string className, Type parentType, string dir)
{
return new DynamicTypeCreatorBase().Create(className, parentType, dir);
}
}
public class PropertyBuilding
{
public PropertyBuilding(PropertyBuilder propertyBuild, MethodBuilder getBuild, MethodBuilder setBuild)
{
propertyBuilder = propertyBuild;
getBuilder = getBuild;
setBuilder = setBuild;
}
public PropertyBuilder propertyBuilder { get; }
public MethodBuilder getBuilder { get; }
public MethodBuilder setBuilder { get; }
}
public class DynamicTypeCreatorBase : IBaseObject, IEmptyObject, IAfterProperty, IAfterAttribute
{
TypeBuilder _tBuilder;
List<PropertyBuilding> _propBuilds = new List<PropertyBuilding>();
AssemblyBuilder _aBuilder;
/// <summary>
/// Begins creating type using the specified name.
/// </summary>
/// <param name="className">Class name for new type</param>
/// <param name="parentType">Name of base class. Use object if none</param>
/// <returns></returns>
public IBaseObject Create(string className, Type parentType)
{
return Create(className, parentType, "");
}
/// <summary>
/// Begins creating type using the specified name and saved in the specified directory.
/// Use this overload to save the resulting .dll in a specified directory.
/// </summary>
/// <param name="className">Class name for new type</param>
/// <param name="parentType">Name of base class. Use object if none</param>
/// <param name="dir">Directory path to save .dll in</param>
/// <returns></returns>
public IBaseObject Create (string className, Type parentType, string dir)
{
_parentType = parentType;
//Define type
AssemblyName _name = new AssemblyName(className);
if (string.IsNullOrWhiteSpace(dir))
{
_aBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(_name, AssemblyBuilderAccess.RunAndSave);
}
else _aBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(_name, AssemblyBuilderAccess.RunAndSave, dir);
ModuleBuilder _mBuilder = _aBuilder.DefineDynamicModule(_name.Name, _name.Name + ".dll");
_tBuilder = _mBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class, parentType);
return this;
}
/// <summary>
/// Adds constructors to new type that match all constructors on base type.
/// Parameters are passed to base type.
/// </summary>
/// <returns></returns>
public IEmptyObject AddPassThroughCtors()
{
foreach(ConstructorInfo _ctor in _parentType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
ParameterInfo[] _params = _ctor.GetParameters();
Type[] _paramTypes = _params.Select(p => p.ParameterType).ToArray();
Type[][] _reqModifiers = _params.Select(p => p.GetRequiredCustomModifiers()).ToArray();
Type[][] _optModifiers = _params.Select(p => p.GetOptionalCustomModifiers()).ToArray();
ConstructorBuilder _ctorBuild = _tBuilder.DefineConstructor(MethodAttributes.Public, _ctor.CallingConvention, _paramTypes, _reqModifiers, _optModifiers);
for (int i = 0; i < _params.Length; i++)
{
ParameterInfo _param = _params[i];
ParameterBuilder _prmBuild = _ctorBuild.DefineParameter(i + 1, _param.Attributes, _param.Name);
if (((int)_param.Attributes & (int)ParameterAttributes.HasDefault) != 0) _prmBuild.SetConstant(_param.RawDefaultValue);
foreach(CustomAttributeBuilder _attr in GetCustomAttrBuilders(_param.CustomAttributes))
{
_prmBuild.SetCustomAttribute(_attr);
}
}
//ConstructorBuilder _cBuilder = _tBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Any, argTypes);
ILGenerator _ctorGen = _ctorBuild.GetILGenerator();
_ctorGen.Emit(OpCodes.Nop);
//arg0=new obj, arg1-arg3=passed params. Push onto stack for call to base class
_ctorGen.Emit(OpCodes.Ldarg_0);
for (int i = 1; i <= _params.Length; i++) _ctorGen.Emit(OpCodes.Ldarg, i);
_ctorGen.Emit(OpCodes.Call, _ctor);
_ctorGen.Emit(OpCodes.Ret);
}
return this;
}
/// <summary>
/// Adds a new property to type with specified name and type.
/// </summary>
/// <param name="name">Name of property</param>
/// <param name="type">Type of property</param>
/// <returns></returns>
public IAfterProperty AddProperty(string name, Type type)
{
//base property
PropertyBuilder _pBuilder = _tBuilder.DefineProperty(name, PropertyAttributes.None, type, new Type[0] { });
//backing field
FieldBuilder _fBuilder = _tBuilder.DefineField($"m_{name}", type, FieldAttributes.Private);
//get method
MethodAttributes _propAttrs = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
MethodBuilder _getBuilder = _tBuilder.DefineMethod($"get_{name}", _propAttrs, type, Type.EmptyTypes);
ILGenerator _getGen = _getBuilder.GetILGenerator();
_getGen.Emit(OpCodes.Ldarg_0);
_getGen.Emit(OpCodes.Ldfld, _fBuilder);
_getGen.Emit(OpCodes.Ret);
//set method
MethodBuilder _setBuilder = _tBuilder.DefineMethod($"set_{name}", _propAttrs, null, new Type[] { type });
ILGenerator _setGen = _setBuilder.GetILGenerator();
_setGen.Emit(OpCodes.Ldarg_0);
_setGen.Emit(OpCodes.Ldarg_1);
_setGen.Emit(OpCodes.Stfld, _fBuilder);
_setGen.Emit(OpCodes.Ret);
_propBuilds.Add(new PropertyBuilding(_pBuilder, _getBuilder, _setBuilder));
return this;
}
/// <summary>
/// Adds an attribute to a property just added.
/// </summary>
/// <param name="attrType">Type of attribute</param>
/// <param name="ctorArgTypes">Types of attribute's cstor parameters</param>
/// <param name="ctorArgs">Values to pass in to attribute's cstor. Must match in type and order of cstorArgTypes parameter</param>
/// <returns></returns>
public IAfterAttribute AddPropertyAttribute(Type attrType, Type[] ctorArgTypes, params object[] ctorArgs)
{
if (ctorArgTypes.Length != ctorArgs.Length) throw new Exception("Type count must match arg count for attribute specification");
ConstructorInfo _attrCtor = attrType.GetConstructor(ctorArgTypes);
for (int i = 0; i < ctorArgTypes.Length; i++)
{
CustomAttributeBuilder _attrBuild = new CustomAttributeBuilder(_attrCtor, ctorArgs);
_propBuilds.Last().propertyBuilder.SetCustomAttribute(_attrBuild);
}
return this;
}
/// <summary>
/// Completes building type, compiles it, and returns the resulting type
/// </summary>
/// <returns></returns>
public Type FinishBuildingType()
{
foreach(var _pBuilder in _propBuilds)
{
_pBuilder.propertyBuilder.SetGetMethod(_pBuilder.getBuilder);
_pBuilder.propertyBuilder.SetSetMethod(_pBuilder.setBuilder);
}
Type _paramsType = _tBuilder.CreateType();
return _paramsType;
}
/// <summary>
/// Completes building type, compiles it, saves it, and returns the resultying type.
/// Assembly is saved in the calling assembly's directory or in the dir specified in the Create method.
/// </summary>
/// <param name="assemblyFileName">Filename of the assembly</param>
/// <returns></returns>
public Type FinishBuildingAndSaveType(string assemblyFileName)
{
Type _newType = FinishBuildingType();
Save(assemblyFileName);
return _newType;
}
#region Helpers
private CustomAttributeBuilder[] GetCustomAttrBuilders(IEnumerable<CustomAttributeData> customAttributes)
{
return customAttributes.Select(attribute => {
object[] attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray();
PropertyInfo[] namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<PropertyInfo>().ToArray();
object[] namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray();
FieldInfo[] namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<FieldInfo>().ToArray();
object[] namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray();
return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues);
}).ToArray();
}
/// <summary>
/// Requires admin privileges.
/// To save in a specified dir, use the Create overload that requires a 'dir' parameter.
/// </summary>
/// <param name="assemblyFileName"></param>
private void Save(string assemblyFileName)
{
string _assemblyName = assemblyFileName;
if (!Path.HasExtension(assemblyFileName) || Path.GetExtension(assemblyFileName).ToLower() != ".dll")
_assemblyName += ".dll";
_aBuilder.Save(_assemblyName);
}
#endregion
}
}
Definition:
public class Product
{
public string ProductID { get; set; }
//product in a product search listing
public string StoreName { get; set; }
public string SearchToken { get; set; }
public Dictionary<string, object> Fields { get; set; }
}
Usage:
var Prod = new Product();
Prod.Fields.Add("MyCustomFieldString", "my value here");
Prod.Fields.Add("MCustomFieldInt", 123);
MessageBox.Show(Prod.Fields[MyCustomFieldString].ToString());
MessageBox.Show(Prod.Fields[MCustomFieldInt].ToString());
You need to clarify (maybe to yourself) what exactly do you need, or I'll say, do you want.
After you compile your code, metadata is generated based on your types\methods\fields. There are many tables (45 to be more specific) to describe your data.
So, If you want to add new properties to some type, you need to add new rows to metadata tables. To add new rows, you need to compile code.
So what is Refelection.Emit? Is a way to dynamically create new data in your app. With this you create a dynamic assembly, dynamic type, dynamic method, add some IL and there you go. But, all of this is in memory. and that not what you want.
So, or dynamically data using Emit \ ExpandoObject, or statically data by adding the properties to code and compile it.
You have also a way of between the run time way to compile way, it's call Mono.Cecil by using it, you can, by code, add the new properties to type, then compile it and load the new dll to your AppDomain or just shutdown the app and re lunch it. It can be automatically and just by code. There is no magic, it will add data to the metadata tables in your assembly, but it can short the way of doing that manually.
But there is a more option that I'm sure you know about it. I you are using Visual Studio, you probably heard about Edit and Continue (EnC). This feature is actually write new code to your running assembly, on the fly add the new data (and only the new) to the metadata.
So, I can't tell you what you should do because I don't know exactly what is your case, and there is cases that people want extreme solution when they not need them, but if you really must the option to add new data to your already running assembly, EnC can be a solution. To use EnC take a look on the feature in Roslyn code (you might want to check their test to understand how to work with). After that, take a look on #Josh Varty example here.
After all I wrote, I really (x3) don't think you need to make something crazy for what you need. I'm sure there is a better, simple way to do it.
I found using DynamicObject Class useful for adding properties dynamically during runtime.
Add a property to your Existing Class like below :
class MyClass
{
public int Id { get; set; }// Existing property
public List<dynamic> Information { get; set; }
// Added above property to handle new properties which will come dynamically
}
//-------- While Implementing ----
MyClass obj = new MyClass();
obj.Id = 1111; // Existing Property
obj.Information = new List<dynamic>();
obj.Information.Add(new ExpandoObject());
obj.Information[0].Name= "Gyan"; // New Property
obj.Information[0].Age = 22; // New Property
I have stored immutable types in a temporary CQRS read store (query/read side, in fact implemented by a simple List with abstraction access layer, I don't want to use a full blown document database at this point). These read stores contains items like the following:
public class SomeItem
{
private readonly string name;
private readonly string description;
public SomeItem(string name, string description)
{
this.name = name;
this.description = description;
}
public string Name
{
get { return this.name; }
}
public string Description
{
get { return this.description; }
}
}
Now I want to change the Name and in a 2nd Command the Description.
These changes should keep the current state, which means for the example above:
// initial state
var someItem = new SomeItem("name", "description");
// update name -> newName
someItem = new SomeItem("newName", someItem.Description);
// update description -> newDescription
someItem = new SomeItem(someItem.Name, "newDescription");
This does look error prone to me if you have several properties... you have to manage keeping the current state. I could add something like Clone() to every type but I think/hope there is something better out there that performs well and is easy to use, I don't want to write much repetive code (lazy programmer). Any suggestions how to improve the code above? The SomeItem class needs to stay immutable (transported through several different threads).
Sadly, there's no simple way in C#. F# has the with keyword, and you could have a look at lenses, but it's all somewhat tedious in C#. The best I can give you is something like this:
class SomeItem
{
private readonly string name;
private readonly string description;
public SomeItem(string name, string description)
{
this.name = name;
this.description = description;
}
public SomeItem With
(
Option<string> name = null,
Option<string> description = null
)
{
return new SomeItem
(
name.GetValueOrDefault(this.name),
description.GetValueOrDefault(this.description)
);
}
}
This allows you to do the updates like
var newItem = oldItem.With(name: "My name!");
I've used this approach with extension methods and T4s to great effect, but even when you write the code manually, it's reasonably reliable - if you add a new field, you must add it to the With as well, so it works quite well.
There's a few more approaches if you are willing to tolerate runtime code generation and reducing type safety, but that's kind of going against the grain IMO.
With C#9 we got the with operator for this purpose.
public record Car
{
public string Brand { get; init; }
public string Color { get; init; }
}
var car = new Car{ Brand = "BMW", Color = "Red" };
var anotherCar = car with { Brand = "Tesla"};
With-expressions When working with immutable data, a common pattern is
to create new values from existing ones to represent a new state. For
instance, if our person were to change their last name we would
represent it as a new object that’s a copy of the old one, except with
a different last name. This technique is often referred to as
non-destructive mutation. Instead of representing the person over
time, the record represents the person’s state at a given time. To
help with this style of programming, records allow for a new kind of
expression; the with-expression:
News in C#9
NOTE
With operator is only supported by records.
Records At the core of classic object-oriented programming is the idea that an object has strong identity and encapsulates mutable state
that evolves over time. C# has always worked great for that, But
sometimes you want pretty much the exact opposite, and here C#’s
defaults have tended to get in the way, making things very laborious.
Recods in C#9
What you are looking for is commonly called the with operator:
// returns a new immutable object with just the single property changed
someItem = { someItem with Name = "newName" };
Unfortunately, unlike F#, C# does not have such an operator (yet?).
Other C# developers are missing this feature as well, which is why someone wrote a Fody extension to do exactly that:
https://github.com/mikhailshilkov/With.Fody
Here's another approach, which implements an UpdateWith method manually but requires an Option<T> helper class. Luaan's answer describes this approach in more detail:
Implementing F#-inspired "with" updates for immutable classes in C#
Simple solution
I also thought about this question. Record's are not suitable for my purposes, since it is necessary to interact with EF Core.
I suggest a simple and low-cost way:
add a copy constructor to the class;
Make properties that change during cloning available for initialization;
clone an object with a change through the copy constructor with an initialization list:
var a = new SomeItem("name", "abracadabra");
var b = new SomeItem(a) {Description="descr"};
Simple code
var a = new SomeItem("name", "abracadabra");
var b = new SomeItem(a) {Description="descr"};
public class SomeItem
{
private string name;
private string description;
public SomeItem(string name, string description)
{
Name = name;
Description = description;
}
public SomeItem(SomeItem another): this(another.Name, another.Description)
{
}
public string Name
{
get => name;
init => name = value;
}
public string Description
{
get => description;
init => description = value;
}
}
Extended solution
If the final type is not known at compile time, then this approach is easy to extend. Let's say there is a class "ValueObject" whose derived types we need to clone.
Note: I apologize for the incorrect translation in some places. English version obtained using google.translate
Additional code
using System.Linq.Expressions;
using Light.GuardClauses;
using JetBrains.Annotations;
using static DotNext.Linq.Expressions.ExpressionBuilder;
using ValueObject = Company.Domain....;
/// <summary>
/// The plagiarizer creates a copy of the object with a change in its individual properties using an initializer
/// </summary>
/// <remarks> The foreign object must define a copy constructor, and mutable members must support initialization </remarks>
public struct Plagiarist {
/// <summary>
/// Object to be copied
/// </summary>
private readonly object _alienObject;
/// <summary>
/// Type <see cref="_alienObject" />
/// </summary>
private Type _type => _alienObject.GetType();
/// <summary>
/// Object parsing Expression
/// </summary>
private readonly ParsingInitializationExpression _parser = new();
public Plagiarist(object alienObject) {
_alienObject = alienObject.MustNotBeNullReference();
if (!CopyConstructorIs())
throw new ArgumentException($"Type {_type.FullName} must implement a copy constructor");
}
/// <summary>
/// Does the object we want to plagiarize have a copy constructor?
/// </summary>
/// <returns>True - there is a copy constructor, otherwise - false</returns>
[Pure]
private bool CopyConstructorIs() {
return _type.GetConstructor(new[] { _type }) is not null;
}
/// <summary>
/// Returns a copy of a foreign object with a change in its individual properties using an initializer
/// </summary>
/// <param name="initializer">
/// <see cref="Expression" /> create an object with initialization of those fields,
/// which need to be changed:
/// <code>() => new T() {Member1 = "Changed value1", Member2 = "Changed value2"}</code>
/// or <see cref="Expression" /> create an anonymous type with initialization of those fields
/// that need to be changed:
/// <code>() => new {Member1 = "Changed value1", Member2 = "Changed value2"}</code>
/// </param>
/// <returns></returns>
[Pure]
public object Plagiarize(Expression<Func<object>> initializer) {
var (newValues, constructParam) = _parser.ParseInitialization(initializer);
var constrCopies = _type.New(_alienObject.Const().Convert(_type));
Expression plagiarist = (newValues.Count, constructParam.Count) switch {
(> 0, _) => Expression.MemberInit(constrCopies, newValues.Values),
(0, > 0) => Expression.MemberInit(constrCopies, ConstructorInInitializationList(constructParam).Values),
_ => constrCopies
};
var plagiarize = Expression.Lambda<Func<object>>(plagiarist).Compile();
return plagiarize();
}
[Pure]
public Dictionary<string, MemberAssignment> ConstructorInInitializationList(
Dictionary<string, Expression> constructorParameters) {
Dictionary<string, MemberAssignment> initializer = new();
const BindingFlags flagReflections = BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public;
var allProperties = _type.GetProperties(flagReflections);
var allFields = _type.GetFields(flagReflections);
foreach (var memberName in constructorParameters.Keys) {
var property = allProperties.FirstOrDefault(s => s.Name ==memberName);
var field = allFields.FirstOrDefault(s => s.Name == memberName);
(MemberInfo member, Type memberType) = (property, field) switch {
({ }, _) => (property, property.PropertyType),
(null, { }) => ((MemberInfo)field, field.FieldType),
_ => throw new ArgumentException($"{_type.FullName} does not contain member {memberName}")
};
initializer[memberName] = Expression.Bind(member, constructorParameters[memberName].Convert(memberType));
}
return initializer;
}
/// <summary>
/// Template "Visitor" for traversing the expression tree in order to highlight
/// initialization expression and constructor
/// </summary>
private class ParsingInitializationExpression : ExpressionVisitor {
private Dictionary<string, MemberAssignment>? _initializer;
private Dictionary<string, Expression>? _initializerAnonym;
/// <summary>
/// Parses the expression tree and returns the initializer and constructor parameters
/// </summary>
/// <param name="initializer"><see cref="Expression" /> to parse</param>
/// <returns> tuple of initializer and constructor</returns>
public ParsedInitialization ParseInitialization(Expression initializer) {
_initializer = new Dictionary<string, MemberAssignment>();
_initializerAnonym = new Dictionary<string, Expression>();
Visit(initializer);
return new ParsedInitialization(_initializer, _initializerAnonym);
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) {
_initializer![node.Member.Name] = node;
return base.VisitMemberAssignment(node);
}
protected override Expression VisitNew(NewExpression node) {
foreach (var (member, value) in node.Members?.Zip(node.Arguments) ??
Array.Empty<(MemberInfo First, Expression Second)>())
_initializerAnonym![member.Name] = value;
return base.VisitNew(node);
}
/// <summary>
/// Type to return values from method <see cref="ParseInitialization" />
/// </summary>
/// <param name="Initializer"></param>
/// <param name="ConstructorParameters"></param>
public record struct ParsedInitialization(Dictionary<string, MemberAssignment> Initializer,
Dictionary<string, Expression> ConstructorParameters);
}
}
public static class ValueObjectPlagiarizer{
/// <summary>
/// Creates a copy of the object with a change in its individual properties using an initializer
/// </summary>
/// <param name="alien">Object to be plagiarized</param>
/// <param name="initializer">
/// <see cref="Expression" /> creating an object of type <typeparamref name="T" />
/// with initialization of those fields that need to be changed:
/// <code>ob.Plagiarize(() => new T() {Member1 = "Changed value1", Member2 = "Changed value2"})</code>
/// or <see cref="Expression" /> create an anonymous type with initialization of those fields that need to be changed:
/// <code>ob.Plagiarize(() => new {Member1 = "Changed value1", Member2 = "Changed value2"})</code>
/// </param>
/// <returns>plagiarism of the object</returns>
public static object Plagiarize<T>(this ValueObject alien, Expression<Func<T>> initializer)
where T : class {
var bodyReduced = initializer.Convert<object>();
var initializerReduced = Expression.Lambda<Func<object>>(bodyReduced, initializer.Parameters);
return new Plagiarist(alien).Plagiarize(initializerReduced);
}
}
Usages
If SomeItem is a descendant of ValueObject
ValueObject a = new SomeItem("name", "abracadabra");
// via type constructor
var b = (SomeItem)a.Plagiarize(()=>new SomeItem(null){Description="descr"});
// anonymous type
var c = (SomeItem)a.Plagiarize(()=>new{Description="descr"});
b.Description.Should().Be("descr"); //true
c.Description.Should().Be("descr"); //true
If what you want to do is (as you commented) update the name of an existing object, a readonly property might be bad design.
Otherwise if its really a new object you want to create, you might want your class to implement some interface with a 'Dispose' method.
I have 2 classes
Class 1
public class baseClass
{
public string prop1{get;set;}
public string prop2{get;set;}
public string prop3{get;set;}
}
Class 2
public class derived:baseClass
{
public string prop4{get;set;}
}
Now when i try to read properties with the following code, but as its obvious it only returns derived class's properties
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(derived));
Is there any way with which i can read properties of derived as well as baseclass
Why not use Reflection?
PropertyInfo[] properties = typeof(derived).GetProperties(BindingFlags.Public | BindingFlags.Instance);
Console.Write(String.Join(Envrironment.NewLine, properties.Select(p => p.Name)));
Actually it works:
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Derived));
for (int i = 0; i < properties.Count; i++)
{
Console.WriteLine(properties[i].Name);
}
Returns:
prop1 prop2 prop3 prop4
And as I have founded in http://referencesource.microsoft.com/, GetProperties() will internally call GetProviderRecursive:
/// <devdoc>
/// This method returns a type description provider, but instead of creating
/// a delegating provider for the type, this will walk all base types until
/// it locates a provider. The provider returned cannot be cached. This
/// method is used by the DelegatingTypeDescriptionProvider to efficiently
/// locate the provider to delegate to.
/// </devdoc>
internal static TypeDescriptionProvider GetProviderRecursive(Type type) {
return NodeFor(type, false);
}
I don't know for what purpose you are trying to fetch properties, but as #Dmitry Bychenko answered, you can use Reflection. You can check the differences of the both ways in this SO link.
Update to your answer:
var result = typeof(Derived).GetProperties()
.Select(prop => new
{
prop.Name,
prop.PropertyType
});
I found solution to read property name as well as its type
var properties = typeof(T).GetFields();
foreach (var prop in properties)
{
var name = prop.Name;
var type = Nullable.GetUnderlyingType(prop.FieldType.FullName) ?? prop.FieldType.FullName);
}
I'm using reflection to iterate over all the members of a given type. This interaction must support inheritance, since most type will be extended as necessary.
I've just found out that the order in which types present themselves when iterating over GetMembers isn't really what I'd expect -- the types of the derived classes appear first, in their current order, and the types of the base classes later, again in their current order.
Source:
using System;
class Program
{
class SomeClass
{
public string First { get; set; }
public int Second;
}
class AnotherClass : SomeClass
{
public int Third { get; set; }
public string Fourth;
}
public static void Main()
{
var obj = new AnotherClass { First = "asd", Second = 10};
foreach (var member in obj.GetType().GetMembers())
{
Console.WriteLine(member.Name);
}
}
}
Output:
get_Third
set_Third
get_First
set_First
Equals
GetHashCode
GetType
ToString
.ctor
Third
First
Fourth
Second
You can check a run here.
I'd like to invert this situation, using reflection to get only types from the base class, then from the derived. Is there any way to do this?
Another question on the same line: when searching members, properties come first and fields second. Anyway to change this behavior as well, or the metadata created will always present in that order?
Thanks!
To access the base type use BaseType property.
To check if a member is declared in the same type, use DeclaringType property:
public static bool DeclaredInType(Type typeToCheck, MemberInfo member)
{
return typeToCheck.Equals(member.DeclaringType);
}
EDIT: you can sort by type by using LINQ:
public static MemberInfo[] SortMembers(IEnumerable<MemberInfo> members)
{
return members.OrderBy(m => m.GetType().Name)
.ToArray();
}
This is not exactly an answer to the OP (and I'm a bit late for that), but I'll just describe what I did, in the hopes someone may find it helpful.
I have a home-made program that serializes to XML. It is driven by a List of CopierField objects that I create that contain the data I need to expedite the serialization. Here is a much-redacted version of that class:
private class CopierField
{
// Name of the field or property and a reference to the declaring Type
public string MemberName;
public Type DeclaringType;
// Reference to the FieldInfo or PropertyInfo object for this field or property. One of
// these will be null and the other non-null.
public FieldInfo MemberInfoForField = null;
public PropertyInfo MemberInfoForProperty = null;
// Ordering of this field, as returned by Type.GetMembers(). This is only used while
// building the List<> of these objects for XML serialization
public int FieldOrder;
/// <summary>
/// Comparison method that can be used to sort a collection of CopierField objects so they
/// are in the order wanted for XML serialization. This ordering is dependent on the depth
/// of derivation, and when that is equal it maintains the original ordering.
/// </summary>
public static readonly Comparison<CopierField> CompareForXml =
delegate(CopierField a, CopierField b)
{
int aDepth = GetTypeDepth(a.DeclaringType);
int bDepth = GetTypeDepth(b.DeclaringType);
if (aDepth != bDepth)
return aDepth.CompareTo(bDepth);
return a.FieldOrder.CompareTo(b.FieldOrder);
};
/// <summary>
/// Method to determine the depth of derivation for a type.
/// </summary>
private static int GetTypeDepth(Type aType)
{
int i = 0;
while (aType.BaseType != null)
{
i++;
aType = aType.BaseType;
}
return i;
}
}
Before serializing an object type to XML the first time I create a List of these objects sorted in the order I want using a method somewhat like this, where the input is a List of the CopierField objects that is based on data from Type.GetMembers().
private static List<CopierField> CreateCopierFieldListForXml(List<CopierField> copierFields)
{
// Build the new list, and note the original ordering as created by Type.GetMembers()
List<CopierField> copierFieldsForXml = new List<CopierField>(copierFields.Count);
for (int listIndex = 0; listIndex < copierFields.Count; listIndex++)
{
CopierField copierField = copierFields[listIndex];
copierField.FieldOrder = listIndex;
copierFieldsForXml.Add(copierField);
}
// Sort the new list as wanted for XML serialization
copierFieldsForXml.Sort(CopierField.CompareForXml);
return copierFieldsForXml;
}
TL;DR
orderby typeof(T).Equals(mi.DeclaringType) ? 1 : -1
will push base memberInfo first, and keep the order defined in the class.
Full answer:
to achieve the same goal, and using the DeclaringType as suggested previously, I defined the following method:
public static IEnumerable<MemberInfo> GetAllFieldsAndPropertiesOfClass<T>()
{
return
from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
let ignoreAttr = (IgnoreSerializationAttribute)Attribute.GetCustomAttribute(mi, typeof(IgnoreSerializationAttribute))
where (mi.MemberType == MemberTypes.Field || mi.MemberType == MemberTypes.Property)
&& (ignoreAttr == null || ignoreAttr != null && !ignoreAttr.Ignore)
orderby typeof(T).Equals(mi.DeclaringType) ? 1 : -1
select mi;
}
In that method, I also defined a custom attribute to explicitely ignore some properties from the serialization:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class IgnoreSerializationAttribute : Attribute
{
public bool Ignore { get; private set; }
public IgnoreSerializationAttribute(bool ignore)
{
Ignore = ignore;
}
}
It is also possible to add an other custom Attribute to define the order, e.g.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class ColumnOrderAttribute : Attribute
{
public int Order { get; private set; }
public ColumnOrderAttribute(int order)
{
Order = order;
}
}
used as follow:
public static IEnumerable<MemberInfo> GetAllFieldsAndPropertiesOfClassOrdered<T>()
{
return
from mi in GetAllFieldsAndPropertiesOfClass<T>()
let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
select mi;
}
I am using those methods to serialize list of objects using other objects to CSV files...