0

I am trying to write a method that can copy an existing class into a new one, but without a reference to the source class.
There are many samples out there, but in my case the source and target objects are defined as a baseclass while they are created as an endclass.

My classes are like this

// my baseclass
public partial class ModelBase
{
    [JsonIgnore]
    public RowStatus Status { get; set; }
}

// one of my endclasses
public class ModelUser: BaseClasses.ModelBase
{
    public int? UserID { get; set; }

    public string UserFirst { get; set; }

    public string UserLastname { get; set; }
}

// and there are many more that derive from ModelBase off course...

What I would like is that in a Base Form (where all other forms will derive from) I can copy an existing model into a new one, without a reference to the existing one.
So I tried to do this using serializing/deserializing, which works well, but only if you know the exact type, and in my BaseForm I do not have the exact type yet, there I work with an instance declared as ModelBase, but created in a derived form as ModelUser for example.

In other words, I have a base form like this

public partial class FormMVBaseDetail
{
    protected ModelBase model = null;
    protected ModelBase _originalModel = null;
    ...

    private void FormMVBaseDetail_Shown(object sender, EventArgs e)
    {
        _originalModel = CloneModel(model);
    }

    protected ModelBase CloneModel(ModelBase model)
    {
        string json = JsonConvert.SerializeObject(model);
        return JsonConvert.DeserializeObject<ModelBase>(json);
    }
}

public partial class FormMVBaseUser : FormMVBaseDetail
{
    model = new ModelUser();
    ...
    // somewhere in the code some properties of model could change...

    private void button1_Click(object sender, EventArgs e)
    {
        // Now I would like that _originalModel is an exact copy of model
        // So _originalModel should be of type ModelUser, not ModelBase
        // How to achieve that without additional code in FormMBaseUser ?
     
        if (_originalModel.UserFirst != model.UserFirst)
        { // do something }
    }
}

The first attempt is this

public ModelBase CloneModel(ModelBase model)
{
    string json = JsonConvert.SerializeObject(model);
    return JsonConvert.DeserializeObject<ModelBase>(json);
}

this has the problem that, allthough the parameter model is created as new ModelUser() the deserialize will make it a ModelBase which only has one public property, the Status.

If I would do this, then it would work, but only for ModelUser

public ModelBase CloneModel(ModelBase model)
{
    string json = JsonConvert.SerializeObject(model);
    return JsonConvert.DeserializeObject<ModelUser>(json);
}

This is not usefull in a baseform.
So the next thing I tried is this

public object CloneModel(ModelBase model)
{
    string json = JsonConvert.SerializeObject(model);
    return JsonConvert.DeserializeObject<object>(json);
}

The idea was to use it like this

ModelBase _originalModel = null;
_originalModel = (ModelBase)CloneModel(CurrentModel);

but for some reason it always deserializes as Newtonsoft.Json.Linq.JObject in stead of object, so this also does not works for me.

How could I achieve this ?

I have read this links, but they did not get me any further

How do I use reflection to call a generic method?
Passing just a type as a parameter in C#
How can I use an expression tree to call a generic method when the Type is only known at runtime?
Use variable as Type

GuidoG
  • 11,359
  • 6
  • 44
  • 79
  • Do you know the concrete type in runtime? For same from install will it be always same concrete type? – Ygalbel Sep 09 '21 at 14:26
  • @Ygalbel no, it can be any class that derives from ModelBase – GuidoG Sep 09 '21 at 14:27
  • You could make the base class generic: public partial class FormMVBaseDetail where T : ModelBase { public T CloneModel(T model) { var json = JsonConvert.SerializeObject(model); return JsonConvert.DeserializeObject(json); } } – Andrew McClement Sep 09 '21 at 14:28
  • Might be tedious (and I might miss something obvious) but why not simply write copy constructors in your endclasses that take the baseclass as argument and perform deep copy of the properties? – Roland Deschain Sep 09 '21 at 14:28
  • @AndrewMcClement That is also in the links I wrote at the bottom of my question, but I can't get it to compile/work. Maybe an example of how to do this in this case ? – GuidoG Sep 09 '21 at 14:29
  • @RolandDeschain Do you have an example of how to do that ? If I have 100 classes derived from ModelBase I don't want to write 100 methods to do this off course – GuidoG Sep 09 '21 at 14:30
  • Yeah, just realized my idea doesn't make much sense in this scenario, sorry. – Roland Deschain Sep 09 '21 at 14:37

2 Answers2

1

You can use the non-generic version of JsonConvert.DeserializeObject()

It take the string in first parameter and the type in second one.

So your clone function will be like this:

public object CloneModel(ModelBase model)
{
    string json = JsonConvert.SerializeObject(model);
    return JsonConvert.DeserializeObject(json, model.GetType());
}
Ygalbel
  • 5,214
  • 1
  • 24
  • 32
1

The generic version of Ygalbel's answer would be this:

public partial class FormMVBaseDetail<T> where T : ModelBase
{
    protected T _model;
    protected T _originalModel;

    public T CloneModel(T model)
    {
       var json = JsonConvert.SerializeObject(model);
       return JsonConvert.DeserializeObject<T>(json);
    }
}

Whether you want the base class to be generic will depend on your needs.

Usage:

public partial class FormMVBaseUser : FormMVBaseDetail<ModelUser>
{
    _model = new ModelUser();
    // Other code as before
Andrew McClement
  • 1,171
  • 5
  • 14
  • How do I call this method for my situation ? – GuidoG Sep 09 '21 at 14:38
  • Sorry, I'll add an example derived class – Andrew McClement Sep 09 '21 at 14:41
  • Though this might work (have not tested yet) I like the answer of Ygalbel more. It seems more simple to me. Maybe someone can explain the differences between these 2 approaches and which would be the better one ? I'll up this answer anyway off course – GuidoG Sep 09 '21 at 14:51
  • @GuidoG The most obvious difference to highlight is that using generics (``) allows the derived types to be used everywhere. No casting from `object` to `ModelBase` is needed when cloning; no casting from `ModelBase` to `ModelUser` is needed within the derived class `FormMVBaseUser`. – Sean Skelly Sep 09 '21 at 17:27