Suppose I have an abstract base class in which I want a CreateCopy method:
public abstract class BaseClass
{
///base stuff
public BaseClass CreateCopy() //or public object, if necessary
{
//?????
}
}
Assume that all derived classes have a parameterless constructor and property fields (that can be) marked with some kind of attribute:
public class DerivedClass : BaseClass
{
[CopiableProperty]
public string Property1 {get; private set;}
[CopiableProperty]
public int Property2 {get; private set;}
//no need to copy
public int Property3 {get; private set;}
//parameterless constructor
public DerivedClass() { }
}
Is it possible with this structure to write the body of CreateCopy() in a way that I can create new instances of the derived objects with the correct CopiableProperty fields?
Naturally I could make a public abstract BaseClass CreateCopy() and force each derived class to care for its own copy, but due to the size and amount of the derived classes, this would bring too much extra effort.
A fairly simple approach could be using generics and reflection:
public abstract class BaseClass
{
// restrict to children of BaseClass
public T CreateCopy<T>() where T: BaseClass, new()
{
var copy = new T();
// get properties that you actually care about
var properties = typeof(T).GetProperties()
.Where(x => x.GetCustomAttribute<CopiablePropertyAttribute>() != null);
foreach (var property in properties)
{
// set the value to the copy from the instance that called this method
property.SetValue(copy, property.GetValue(this));
}
return copy;
}
}
public class DerivedClass : BaseClass
{
[CopiableProperty]
public string Property1 { get; set; }
[CopiableProperty]
public int Property2 { get; set; }
public int Property3 { get; set; }
public override string ToString()
{
return $"{Property1} - {Property2} - {Property3}";
}
}
static void Main(string[] args)
{
var original = new DerivedClass
{
Property1 = "Hello",
Property2 = 123,
Property3 = 500
};
var copy = original.CreateCopy<DerivedClass>();
Console.WriteLine(original);
Console.WriteLine(copy);
Console.ReadLine();
}
This would print:
Hello - 123 - 500
Hello - 123 - 0
Another approach would be to take advantage of a serialization library, if you don't mind the dependency:
public abstract class BaseClass
{
public BaseClass CreateCopy()
{
string serialized = JsonConvert.SerializeObject(this);
var actualType = GetType();
return JsonConvert.DeserializeObject(serialized, actualType) as BaseClass;
}
}
public class DerivedClass : BaseClass
{
public string Property1 { get; set; }
public int Property2 { get; set; }
[JsonIgnore]
public int Property3 { get; set; }
//parameterless constructor
public DerivedClass() { }
public override string ToString()
{
return $"{Property1} - {Property2} - {Property3}";
}
}
My solution uses serialization/deserialization, using JSON.NET nuget package.
No need for a method in the base class, you can use an extension method instead (adapted from this answer):
using Newtonsoft.Json;
public static class ObjectExtensions
{
public static T Clone<T>(this T source)
{
var serialized = JsonConvert.SerializeObject(source);
var clone = JsonConvert.DeserializeObject<T>(serialized);
return clone;
}
}
And then use attributes to control which properties should be copied or not - example:
using Newtonsoft.Json;
public class DerivedClass : BaseClass
{
public string Property1 { get; set; }
public int Property2 { get; set; }
[JsonIgnore]
public int Property3 { get; set; }
}
Using the code:
var obj1 = new DerivedClass
{
Property1 = "Abc",
Property2 = 999,
Property3 = 123
};
DerivedClass clone = obj1.Clone();
Result - as you can see, Property3 has the default value in the cloned object:
Iterate through all properties in type and check your attribute with GetCustomAttributes. See code:
public BaseClass CreateCopy()
{
var type = GetType();
var result = Activator.CreateInstance(type);
foreach (var propertyInfo in type.GetProperties())
{
var skipThisProperty = !propertyInfo.GetCustomAttributes(
typeof(CopiablePropertyAttribute), false)
.Any();
if (skipThisProperty)
continue;
var value = propertyInfo.GetValue(this, null);
propertyInfo.SetValue(result, value, null);
}
return (BaseClass) result;
}
Please pay attention to null parameter in GetValue and SetValue. If your property is indexer, you need to pass a correct value as last argument
Related
I have the following construction of classes, here simplified as child classes of a 'mother' class called DataClass, which also contains one simple method:
public class DataClass
{
public int num { get; set; }
public string code { get; set; }
public PartClass part { get; set; }
public MemberClass member { get; set; }
public int Count()
{
Type t = typeof(DataClass);
return typeof(DataClass).GetProperties().Length;
}
}
public class PartClass
{
public int seriesNum { get; set; }
public string seriesCode { get; set; }
}
public class MemberClass
{
public int versionNum { get; set; }
public SideClass side { get; set; }
}
public class SideClass
{
public string firstDetail { get; set; }
public string secondDetail { get; set; }
public bool include { get; set; }
}
The issue is, I want to refactor the method so that it can give me an accurate counting of all properties found, including the ones in nested or child classes. In the above example, it only counts properties of DataClass, while I wanted it to return 2 for DataClass + 2 for PartClass + 1 for MemberClass + 3 for SideClass, sums up to 8 properties you may set through DataClass.
Can someone help me with this?
You can introduce interface with Count() method
public interface ICountable
{
int Count();
}
And use this interface to mark all types, which properties are participating in Count() calculation.
You can see the generic abstract class to implement this interface below. Generic T parameter is type whose properties need to be calculated. You implement a calculation logic only once and inherit this class where needed. You also go through all of properties, implementing ICountable, to calculate them as well (some kind of recursion)
public abstract class Countable<T> : ICountable
{
public int Count()
{
Type t = typeof(T);
var properties = t.GetProperties();
var countable = properties.Select(p => p.PropertyType).Where(p => typeof(ICountable).IsAssignableFrom(p));
var sum = countable.Sum(c => c.GetProperties().Length);
return properties.Length + sum;
}
}
and inherit it in your classes
public class DataClass : Countable<DataClass>
{
...
}
public class PartClass : Countable<PartClass>
{
...
}
public class MemberClass : Countable<MemberClass>
{
...
}
public class SideClass : Countable<SideClass>
{
...
}
And this is for the test
var dataClass = new DataClass();
var count = dataClass.Count();
It returns 8 as expected
I have a requirement to order several lists by the same value. But, for whatever reason, these lists contain objects of different types which share this value. Let's call it ChildID.
The simplified model code would look something like this:
public class Child
{
public string ChildID { get; set; }
}
public class Parent
{
public Child Child { get; set; }
}
public class OtherClassID
{
public int ID { get; set; }
public string ChildID { get; set; }
}
public class SomeOtherClass
{
public OtherClassID ID { get; set; }
}
So, in order to avoid code duplication, I tried this:
public interface IHasChildID
{
string GetChildID();
}
public class Child : IHasChildID
{
public string ChildID { get; set; }
public string GetChildID()
{
return ChildID;
}
}
public class Parent : IHasChildID
{
public Child Child { get; set; }
public string GetChildID()
{
return Child.ChildID;
}
}
public class OtherClassID
{
public int ID { get; set; }
public string ChildID { get; set; }
}
public class SomeOtherClass : IHasChildID
{
public OtherClassID ID { get; set; }
public string GetChildID()
{
return ID.ChildID;
}
}
And when I created a helper class with a helper method which takes an interface as a parameter, I expected it to work:
public static class ChildOrderHelper
{
public static IEnumerable<IHasChildID> OrderChildren(IEnumerable<IHasChildID> children)
{
var childrenList = children.ToList();
//do some splitting, ordering and conatenation of lists
return orderedList;
}
}
But, on every helper call I get an error:
List<Child> originalList = GetChildren(); // whatever
// some lines of code
var orderedList = ChildOrderHelper.OrderChildren(originalList).ToList(); // error
Error CS1503 Argument 1: cannot convert from
'System.Collections.Generic.List<NamespaceOne.Child>' to
'System.Collections.Generic.List<NamespaceTwo.IHasChildID>'
And so for every helper call, no matter the type.
One thing to note is that I've given an example with three distinct types that have this value and need to be ordered by it. In the project, there is probably 10 or more.
I guess there is something fundamental I don't yet understand about interface usage, but any help would be appreciated on this matter.
I'm not entirely sure what your overall use case is, but maybe it would be beneficial to make the OrderChildren method generic, as follows:
public static class ChildOrderHelper
{
public static IEnumerable<T> OrderChildren<T>(IEnumerable<T> children) where T : IHasChildID
{
var childrenList = children.ToList();
//just a simple example of what I'm guessing the method could do...
return childrenList.OrderBy(c => c.GetChildID()).ToList();
}
}
And call it as follows:
List<Child> originalList = GetChildren();
List<Child> orderedList = ChildOrderHelper.OrderChildren<Child>(originalList).ToList();
The approach can be taken like defining an interface and then implemenint that one in all the required classes or a base class that can lookup the child id.
Below is a sample of the source code.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections;
public class Program
{
public static void Main()
{
var parents = new List<Parent>();
parents.Add(new Parent{ChildId = "123"});
parents.Add(new Parent{ChildId = "321"});
parents.Add(new Parent{ChildId = "456"});
var result = ChildHelpers.OrderChildren(parents);
foreach(var res in result) {
Console.WriteLine(res.ChildId);
}
Console.WriteLine("Hello World");
}
}
public interface IChild {
string ChildId {get;set;}
}
public class Child : IChild {
public string Name {get;set;}
public string ChildId {get;set;}
}
public class Parent : IChild {
public Parent() {
child = new Child();
}
public Child child {get;set;}
public string ChildId {
get{
return child.ChildId;
}
set{
child.ChildId = value;
}
}
}
public class AnotherChild : IChild {
public string Description{get;set;}
public string ChildId {get;set;}
}
public static class ChildHelpers {
public static IEnumerable<IChild> OrderChildren(IEnumerable<IChild> children)
{
return children.OrderBy(c=>c.ChildId).AsEnumerable();
}
}
If you would like to playaround with this sample and see other options if required, please refer this link.
I am trying to create a list of a class with the property with T:
class Test<T> where T : IClass, new()
{
public T Actor { get { return new T(); } }
public eEnum { get; set; }
public String Str { get; set; }
}
The above is an example class, how can I create a list of the above class?
I have tried the below with no avail:
List<Test<IClass>> list = new List<IClass>();
Is there a way to achieve creating a list like I am trying to generate?
Since you have added the generic type constraint new(), you must provide a type that has a public parameterless constructor. An interface doesn't have a constructor. Therefore you must indicate a class. E.g.
List<Test<MyActorClass>> list = new List<Test<MyActorClass>>();
Or drop this new() constraint and instead add a generic factory method
class Test
{
public T CreateActor<T>()
where T : IClass, new()
{
return new T();
}
public MyEnum eEnum { get; set; }
public string Str { get; set; }
}
And simply create a list List<Test>.
Or supply a concrete actor type through constructor injection:
class Test
{
public Test(IClass actor)
{
Actor = actor;
}
public IClass Actor { get; }
public MyEnum eEnum { get; set; }
public string Str { get; set; }
}
An even more advanced construction is to use a non-generic abstract base class and to derive a generic one from it
abstract class Test
{
private IClass _actor;
public IClass Actor
{
get {
if (_actor == null) {
_actor = CreateActor();
}
return _actor;
}
}
public MyEnum eEnum { get; set; }
public string Str { get; set; }
protected abstract IClass CreateActor(); // We implement it in the generic class.
}
class Test<T> : Test
where T : IClass, new()
{
public new T Actor // Hides inherited member.
{
get { return (T)base.Actor; }
}
protected override IClass CreateActor()
{
return new T();
}
}
The list would again be of type List<Test>. This has the advantage that you can add different types of classes deriving from Test to the list and at the same time you have a strongly typed actor when accessing it through a concrete Test<T>.
Using .net XmlSerializer and the following structure:
public class SomeClass {
[XmlElement("some-string")]
public string SomeString { get; set; }
}
I need the above to produce :
<someclass>
<some-string alt-name="someotherstring">
StringValue
</some-string>
</someclass>
But i dont want to have to define types for somestring, some int, somebool, yetanotherstring etc every time i want to add a standard type as a porperty to my classes.
Any way I can override xlement to handle this maybe?
Produce wrappers for base types and conversion operators to alleviate object construction:
[Serializable()]
public partial class StringWrapper
{
[XmlAttribute("alt-name")]
public string altname { get; set; }
[XmlText()]
public string Value { get; set; }
public static implicit operator string (StringWrapper sw) { return sw.Value; }
public static implicit operator StringWrapper (string s) {
return new StringWrapper() { altname = "someotherstring", Value = s };
}
}
Use wrappers instead of base types where needed:
[Serializable()]
[XmlRoot(Namespace = "someclass", IsNullable = false)]
public class someclass
{
[XmlElement("some-string")]
public StringWrapper somestring { get; set; }
}
Use it like:
var srlz = new XmlSerializer(typeof(someclass));
srlz.Serialize(Console.Out, new someclass() { somestring = "StringValue" });
The only way to do that via XmlSerializer is:
[XmlRoot("someclass")]
public class SomeClass {
[XmlElement("some-string")]
public SomeOtherClass Foo {get;set;}
}
public class SomeOtherClass {
[XmlText]
public string Text {get;set;}
[XmlAttribute("alt-name")]
public string Bar {get;set;}
}
Alternatively: use XmlDocument / XDocument instead of XmlSerializer.
I'm trying to cache some classes in a List.
Because this class has a generic Property, I created a none-generic Type of the class which is the Type of this List.
So my BOs looks like this:
public class Model<T> : Model where T : class
{
public T Cls
{
get { return (T) ClsObject; }
set { ClsObject = value; }
}
}
public class Model
{
public List<ModelProperty> Properties { get; set; }
public string ModelName { get; set; }
public Type ClsType { get; set; }
public object ClsObject { get; set; }
}
So here's the Caching-Class:
private static List<Model> CachedModels {get; set;}
public static Model<T> GetCachedVersion<T>(this T cls) where T : class
{
var ret = CachedModels.FirstOrDefault(x => x.ClsType == typeof(T));
return ret != null ? (Model<T>)ret : null;
}
But the casting from the GetCachedVersion-Method crashes and I don't understand why.
Thanks for any tips!
If you are looking for the first object of type Model<T> you could change your code to
public static Model<T> GetCachedVersion<T>(this T cls) where T : class
{
return CachedModels.OfType<T>.FirstOrDefault();
}
There are a number of things you might wish to consider. Because if things were as you expected them to be your code should work no different from this.
1) tie the ClsType to the ClsObject or T
2) remove the setter (or otherwise hide it from external code) of ClsObject since it violates the invariant of Cls. You can set the Cls property to something that's not a T
public class Model {
public List<ModelProperty> Properties { get; set; }
public string ModelName { get; set; }
public virtual Type ClsType { get {
ClsObject.GetType();
} }
public object ClsObject { get; protected set; }
}
public class Model<T> : Model {
public override Type ClsType { get{
return typeof(T);
}}
public T Cls
{
get { return (T) ClsObject; }
set { ClsObject = value; }
}
}
1st guesses:
Are you by any chance using a nullable type anywhere in your code like Model?
See if you are setting ClsType properly.
It looks like you would have to create a Helper-Class which created the generic Type through reflections. There's no other way to downcast besides hardcode it.