Cast class into another class or convert class to another
Asked Answered
F

11

70

My question is shown in this code

I have class like that

public class MainCS
{
  public int A;
  public int B;
  public int C;
  public int D; 
}

public class Sub1
{
  public int A;
  public int B;
  public int C;
}


public void MethodA(Sub1 model)
{
  MainCS mdata = new MainCS() { A = model.A, B = model.B, C = model.C };   

  // is there a way to directly cast class Sub1 into MainCS like that    
  mdata = (MainCS) model;    
}
Flub answered 8/9, 2010 at 23:40 Comment(3)
There are existing lightweight mapper libraries written already for exactly this purpose. They handle a lot more edge cases. You can google it.Trish
First and easy solution: automapper.orgHole
why not write sub1 constructor that accept maincs as argument and return new sub1 after initialize sub1's field with maincs's field?Messeigneurs
U
51

What he wants to say is:

"If you have two classes which share most of the same properties you can cast an object from class a to class b and automatically make the system understand the assignment via the shared property names?"

Option 1: Use reflection

Disadvantage : It's gonna slow you down more than you think.

Option 2: Make one class derive from another, the first one with common properties and other an extension of that.

Disadvantage: Coupled! if your're doing that for two layers in your application then the two layers will be coupled!

Let there be:

class customer
{
    public string firstname { get; set; }
    public string lastname { get; set; }
    public int age { get; set; }
}
class employee
{
    public string firstname { get; set; }
    public int age { get; set; } 
}

Now here is an extension for Object type:

public static T Cast<T>(this Object myobj)
{
    Type objectType = myobj.GetType();
    Type target = typeof(T);
    var x = Activator.CreateInstance(target, false);
    var z = from source in objectType.GetMembers().ToList()
        where source.MemberType == MemberTypes.Property select source ;
    var d = from source in target.GetMembers().ToList()
        where source.MemberType == MemberTypes.Property select source;
    List<MemberInfo> members = d.Where(memberInfo => d.Select(c => c.Name)
       .ToList().Contains(memberInfo.Name)).ToList();
    PropertyInfo propertyInfo;
    object value;
    foreach (var memberInfo in members)
    {
        propertyInfo = typeof(T).GetProperty(memberInfo.Name);
        value = myobj.GetType().GetProperty(memberInfo.Name).GetValue(myobj,null);

        propertyInfo.SetValue(x,value,null);
    }   
    return (T)x;
}  

Now you use it like this:

static void Main(string[] args)
{
    var cus = new customer();
    cus.firstname = "John";
    cus.age = 3;
    employee emp =  cus.Cast<employee>();
}

Method cast checks common properties between two objects and does the assignment automatically.

Undeceive answered 9/9, 2010 at 1:42 Comment(5)
Nice solution, but as you said, overhead and complexity :)Illusory
I guess you have missed to make use of 'z' variable. It should be used when initializing 'members' var i.e. List<MemberInfo> members = z.Where(memberInfo => d.Select(c => c.Name) .ToList().Contains(memberInfo.Name)).ToList();Barouche
The extension method Cast will cause conflict with System.Linq Enumerable.Cast<TResult> and could result in an error that is hard to debug. Changing the name is recommended.Oswin
The Cast method is option1, therefore using reflection, am I understanding that correctly?Asbestosis
It's better to name this method Convert rather than Cast because casting and converting are different operations. Please see this answer: https://mcmap.net/q/224873/-c-dynamic-runtime-castTrousers
O
115

Use JSON serialization and deserialization:

using Newtonsoft.Json;

Class1 obj1 = new Class1();
Class2 obj2 = JsonConvert.DeserializeObject<Class2>(JsonConvert.SerializeObject(obj1));

Or:

public class Class1
{
    public static explicit operator Class2(Class1 obj)
    {
        return JsonConvert.DeserializeObject<Class2>(JsonConvert.SerializeObject(obj));
    }
}

Which then allows you to do something like

Class1 obj1 = new Class1();
Class2 obj2 = (Class2)obj1;
Ovenware answered 22/7, 2016 at 5:45 Comment(3)
Yes, using Newtonsoft json serializer is quite simple and efficient. There is .Net serializer but I found Newtonsoft do well over .Net json serializer. I found this link which gives a brief comparision newtonsoft.com/json/help/html/JsonNetVsDotNetSerializers.htmBarouche
This is exactly what I needed, I was trying to cast json objects returned from api call and cast into strong types, never put this together with class types, GREAT solution!Albuminuria
Somehow first method work as usual but not second when defining inside partial classes, any hint?Fives
I
71

You have already defined the conversion, you just need to take it one step further if you would like to be able to cast. For example:

public class sub1
{
    public int a;
    public int b;
    public int c;

    public static explicit operator maincs(sub1 obj)
    {
        maincs output = new maincs() { a = obj.a, b = obj.b, c = obj.c };
        return output;
    }
}

Which then allows you to do something like

static void Main()
{
    sub1 mySub = new sub1();
    maincs myMain = (maincs)mySub;
}
Infective answered 8/9, 2010 at 23:48 Comment(3)
Are there any implications for converting/casting from one class to another with the exact props using your code?Savoury
The problem with this is that it actually is very implicit. You are overriding a very common operation in C. The only way to see that this is a custom cast is to look at the class itself. So this actually becomes unreadable when you do this on a large scale. It tricks the developers reading it.Harrie
how do you do this if you don't own the sub1 class? Is there a way to do this in the maincs class?Shalna
U
51

What he wants to say is:

"If you have two classes which share most of the same properties you can cast an object from class a to class b and automatically make the system understand the assignment via the shared property names?"

Option 1: Use reflection

Disadvantage : It's gonna slow you down more than you think.

Option 2: Make one class derive from another, the first one with common properties and other an extension of that.

Disadvantage: Coupled! if your're doing that for two layers in your application then the two layers will be coupled!

Let there be:

class customer
{
    public string firstname { get; set; }
    public string lastname { get; set; }
    public int age { get; set; }
}
class employee
{
    public string firstname { get; set; }
    public int age { get; set; } 
}

Now here is an extension for Object type:

public static T Cast<T>(this Object myobj)
{
    Type objectType = myobj.GetType();
    Type target = typeof(T);
    var x = Activator.CreateInstance(target, false);
    var z = from source in objectType.GetMembers().ToList()
        where source.MemberType == MemberTypes.Property select source ;
    var d = from source in target.GetMembers().ToList()
        where source.MemberType == MemberTypes.Property select source;
    List<MemberInfo> members = d.Where(memberInfo => d.Select(c => c.Name)
       .ToList().Contains(memberInfo.Name)).ToList();
    PropertyInfo propertyInfo;
    object value;
    foreach (var memberInfo in members)
    {
        propertyInfo = typeof(T).GetProperty(memberInfo.Name);
        value = myobj.GetType().GetProperty(memberInfo.Name).GetValue(myobj,null);

        propertyInfo.SetValue(x,value,null);
    }   
    return (T)x;
}  

Now you use it like this:

static void Main(string[] args)
{
    var cus = new customer();
    cus.firstname = "John";
    cus.age = 3;
    employee emp =  cus.Cast<employee>();
}

Method cast checks common properties between two objects and does the assignment automatically.

Undeceive answered 9/9, 2010 at 1:42 Comment(5)
Nice solution, but as you said, overhead and complexity :)Illusory
I guess you have missed to make use of 'z' variable. It should be used when initializing 'members' var i.e. List<MemberInfo> members = z.Where(memberInfo => d.Select(c => c.Name) .ToList().Contains(memberInfo.Name)).ToList();Barouche
The extension method Cast will cause conflict with System.Linq Enumerable.Cast<TResult> and could result in an error that is hard to debug. Changing the name is recommended.Oswin
The Cast method is option1, therefore using reflection, am I understanding that correctly?Asbestosis
It's better to name this method Convert rather than Cast because casting and converting are different operations. Please see this answer: https://mcmap.net/q/224873/-c-dynamic-runtime-castTrousers
P
9

You could change your class structure to:

public class maincs : sub1
{
   public int d; 
}

public class sub1
{
   public int a;
   public int b;
   public int c;
}

Then you could keep a list of sub1 and cast some of them to mainc.

Pestle answered 8/9, 2010 at 23:46 Comment(2)
This doesn't compile either. Maybe you forgot the class keywordSedulous
Oops, that's what I get for copy/paste.Pestle
H
3

By using following code you can copy any class object to another class object for same name and same type of properties.

public class CopyClass
{
    /// <summary>
    /// Copy an object to destination object, only matching fields will be copied
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sourceObject">An object with matching fields of the destination object</param>
    /// <param name="destObject">Destination object, must already be created</param>
    public static void CopyObject<T>(object sourceObject, ref T destObject)
    {
        //  If either the source, or destination is null, return
        if (sourceObject == null || destObject == null)
            return;

        //  Get the type of each object
        Type sourceType = sourceObject.GetType();
        Type targetType = destObject.GetType();

        //  Loop through the source properties
        foreach (PropertyInfo p in sourceType.GetProperties())
        {
            //  Get the matching property in the destination object
            PropertyInfo targetObj = targetType.GetProperty(p.Name);
            //  If there is none, skip
            if (targetObj == null)
                continue;

            //  Set the value in the destination
            targetObj.SetValue(destObject, p.GetValue(sourceObject, null), null);
        }
    }
}

Call Method Like,

ClassA objA = new ClassA();
ClassB objB = new ClassB();

CopyClass.CopyObject(objOfferMast, ref objB);

It will copy objA into objB.

Hobnail answered 4/10, 2013 at 6:31 Comment(2)
Please be aware that if you're using this solution, you might run into issues when classes have the same name for properties, but with different types. For example: public class A { public int Age{get;set;}} and public class B { public string Age{get;set;}} Will throw an exception, if you're trying to convert from A to BIllusory
This type of conversion could also lead to some huge performance issues. Use it carefully and certainly not in for-loopsKalikalian
C
2

You can provide an explicit overload for the cast operator:

public static explicit operator maincs(sub1 val)
{
    var ret = new maincs() { a = val.a, b = val.b, c = val.c };
    return ret;
}

Another option would be to use an interface that has the a, b, and c properties and implement the interface on both of the classes. Then just have the parameter type to methoda be the interface instead of the class.

Cohosh answered 8/9, 2010 at 23:50 Comment(0)
E
2

Using this code you can copy any class object to another class object for same name and same type of properties.

JavaScriptSerializer JsonConvert = new JavaScriptSerializer(); 
string serializeString = JsonConvert.Serialize(objectEntity);
objectViewModel objVM = JsonConvert.Deserialize<objectViewModel>(serializeString);
Elsie answered 20/6, 2018 at 10:59 Comment(0)
B
1

There are some great answers here, I just wanted to add a little bit of type checking here as we cannot assume that if properties exist with the same name, that they are of the same type. Here is my offering, which extends on the previous, very excellent answer as I had a few little glitches with it.

In this version I have allowed for the consumer to specify fields to be excluded, and also by default to exclude any database / model specific related properties.

    public static T Transform<T>(this object myobj, string excludeFields = null)
    {
        // Compose a list of unwanted members
        if (string.IsNullOrWhiteSpace(excludeFields))
            excludeFields = string.Empty;
        excludeFields = !string.IsNullOrEmpty(excludeFields) ? excludeFields + "," : excludeFields;
        excludeFields += $"{nameof(DBTable.ID)},{nameof(DBTable.InstanceID)},{nameof(AuditableBase.CreatedBy)},{nameof(AuditableBase.CreatedByID)},{nameof(AuditableBase.CreatedOn)}";

        var objectType = myobj.GetType();
        var targetType = typeof(T);
        var targetInstance = Activator.CreateInstance(targetType, false);

        // Find common members by name
        var sourceMembers = from source in objectType.GetMembers().ToList()
                                  where source.MemberType == MemberTypes.Property
                                  select source;
        var targetMembers = from source in targetType.GetMembers().ToList()
                                  where source.MemberType == MemberTypes.Property
                                  select source;
        var commonMembers = targetMembers.Where(memberInfo => sourceMembers.Select(c => c.Name)
            .ToList().Contains(memberInfo.Name)).ToList();

        // Remove unwanted members
        commonMembers.RemoveWhere(x => x.Name.InList(excludeFields));

        foreach (var memberInfo in commonMembers)
        {
            if (!((PropertyInfo)memberInfo).CanWrite) continue;

            var targetProperty = typeof(T).GetProperty(memberInfo.Name);
            if (targetProperty == null) continue;

            var sourceProperty = myobj.GetType().GetProperty(memberInfo.Name);
            if (sourceProperty == null) continue;

            // Check source and target types are the same
            if (sourceProperty.PropertyType.Name != targetProperty.PropertyType.Name) continue;

            var value = myobj.GetType().GetProperty(memberInfo.Name)?.GetValue(myobj, null);
            if (value == null) continue;

            // Set the value
            targetProperty.SetValue(targetInstance, value, null);
        }
        return (T)targetInstance;
    }
Breeks answered 4/3, 2018 at 22:42 Comment(1)
that will work as long as you dont have ENUMS in your object as propertiesVertu
T
1

I tried to use the Cast Extension (see https://stackoverflow.com/users/247402/stacker) in a situation where the Target Type contains a Property that is not present in the Source Type. It did not work, I'm not sure why. I refactored to the following extension that did work for my situation:

public static T Casting<T>(this Object source)
{
    Type sourceType = source.GetType();
    Type targetType = typeof(T);
    var target =  Activator.CreateInstance(targetType, false);
    var sourceMembers = sourceType.GetMembers()
        .Where(x => x.MemberType  == MemberTypes.Property)
        .ToList();
    var targetMembers = targetType.GetMembers()
        .Where(x => x.MemberType == MemberTypes.Property)
        .ToList();
    var members = targetMembers
        .Where(x => sourceMembers
            .Select(y => y.Name)
                .Contains(x.Name));
    PropertyInfo propertyInfo;
    object value;
    foreach (var memberInfo in members)
    {
        propertyInfo = typeof(T).GetProperty(memberInfo.Name);
        value = source.GetType().GetProperty(memberInfo.Name).GetValue(source, null);
        propertyInfo.SetValue(target, value, null);
    }
    return (T)target;
}

Note that I changed the name of the extension as the Name Cast conflicts with results from Linq. Hat tip https://stackoverflow.com/users/2093880/usefulbee

Theodora answered 25/12, 2020 at 16:44 Comment(0)
H
0
var obj =  _account.Retrieve(Email, hash);

AccountInfoResponse accountInfoResponse = new AccountInfoResponse();

if (obj != null)
{               
   accountInfoResponse = 
   JsonConvert.
   DeserializeObject<AccountInfoResponse>
   (JsonConvert.SerializeObject(obj));
}

image description

Hearse answered 23/11, 2018 at 6:21 Comment(0)
U
0

I developed a Class ObjectChanger that contains the functions ConvertToJson, DeleteFromJson, AddToJson, and ConvertToObject. These functions can be used to convert a C# object to JSON which properties can then be removed or added accordingly. Afterwards the adjusted JSON object can simply be converted to a new object using ConvertToObject function. In the sample code below the class "AtoB" utilizes ObjectChanger in its GetAtoB() function:

using System.Collections.Generic;
using Newtonsoft.Json;
using Nancy.Json;
namespace YourNameSpace
{
public class A
{
    public int num1 { get; set; }
    public int num2 { get; set; }
    public int num3 { get; set; }
}
public class B//remove num2 and add num4
{
    public int num1 { get; set; }
    public int num3 { get; set; }
    public int num4 { get; set; }
}
/// <summary>
/// This class utilizes ObjectChanger to illustrate how
/// to convert object of type A to one of type B
/// by converting A to a Json Object, manipulating the JSON
/// and then converting it to object of type B
/// </summary>
public class AtoB
{
    public dynamic GetAtoB()
    {
        A objectA = new A
        {
            num1 =1, num2 =2,num3 =3
        };
        //convert "objectA" to JSON object "jsonA"
        dynamic jsonA = ObjectChanger.ConvertToJson(objectA);
        //remove num2 from jsonA
        ObjectChanger.DeleteFromJson(jsonA, "num2");
        //add property num4 with value 4 to jsonA
        ObjectChanger.AddToJson(jsonA, "num4", 4);

        B objectB = ObjectChanger.ConvertToObject<B>(jsonA);

        return objectB;
       
        //note: Above DeleteFromJson not needed if the 
        //property(e.g "num2") doesn't exist in objectB   
        //the jsonA will still keep the num2 but when
        //ConvertToObject is called the objectB will only get 
        //populated with the relevant fields.
    }
}
public class ObjectChanger
{
    /// <summary>
    /// Converts a provided class to JsonObject
    /// sample use: dynamic r = ObjectChanger.ConvertToJson(providedObj);
    /// </summary>
    public static dynamic ConvertToJson(dynamic providedObj)
    {
        JsonSerializerSettings jss = new JsonSerializerSettings();
        jss.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        //https://mcmap.net/q/64045/-json-net-error-self-referencing-loop-detected-for-type
        return JsonConvert.DeserializeObject<System.Dynamic.ExpandoObject>
               (JsonConvert.SerializeObject(providedObj,jss));
        
    }
    /// <summary>
    /// Deletes Property from Json Object
    /// sample use: dynamic r = ObjectChanger.ConvertToJson(providedObj);
    /// ((IDictionary<string, object>)r).Remove("keyvalue");
    /// </summary>
    public static dynamic DeleteFromJson(dynamic providedObj, string keyvalue)
    {
        ((IDictionary<string, object>)providedObj).Remove(keyvalue);
        return providedObj;
    }
    /// <summary>
    /// Adds Property to provided Json Object
    /// </summary>
    /// <param name="providedObj"></param>
    /// <param name="key"></param>
    /// <param name="keyvalue"></param>
    /// <returns>Returns updated Object</returns>
    public static dynamic AddToJson(dynamic providedObj, string key, 
    dynamic keyvalue)
    {
        ((IDictionary<string, object>)providedObj).Add(key, keyvalue);
        return providedObj;
    }
    /// <summary>
    /// Converts provided object providedObj 
    /// to an object of type T
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="providedObj"></param>
    /// <returns></returns>
    public static T ConvertToObject<T>(dynamic providedObj)
    {
        var serializer = new JavaScriptSerializer();
        var json = serializer.Serialize(providedObj);
        var c = serializer.Deserialize<T>(json);
        return c;
    }
}
}
Upchurch answered 31/12, 2021 at 2:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.