Automatically expand an object to some inheritance class

Suppose I have the following declarations:

public class ClassA { public string FieldA = "Something first"; } public class ClassB : ClassA { public string FieldB = "Something second"; } 

As we all know, a ClassB object can be used anywhere ClassA is required. For example, the method

 public void DoSthWithObject(ClassA a); 

can be called like this:

 ClassB b = new ClassB(); DoSthWithObject(b); 

It is also possible to pass an inherited type as a parent type:

 ClassA a = (b as ClassA); 

So, the compiler / structure knows how to “use” ClassB data where ClassA is required.

And now the question is: can I somehow do the opposite? Suppose I have a variable of type ClassA , and I want to start using it as ClassB but at the same time keep all the fields that ClassB inherits from ClassA and not instantiate a new object? I understand that placing ClassB fields requires an extension of the memory allocated for the ClassA object.

Simply put, I want to do something like this:

 ClassA a = new ClassA(); ClassB b = (a as ClassB); // some magic happens here 

(I know that I can implement IConvertible and use Convert or provide some IConvertible cloning mechanism, but I wanted to avoid such solutions.)

EDIT

It seems that the answers concentrate on offering alternative solutions or warning me that I even think that I should do what I want, I decided to slightly change my question and offer generosity so that maybe I can get more quality answers.

First of all, I want it to be agnostic.

Secondly: I really do understand that not every instrument is a hammer, and I can (I hope) distinguish Jack from Jane. I also do not need a lesson on fundamental OOP (if someone cannot actually prove that I am making a fundamental conceptual error, which I obviously don’t know about right now). What I want to know is if and why automatic conversion between parents and subclasses is logically impossible , and if it is possible (and, as the comments suggest, languages ​​exist where possible), then why is it dangerous / full of flaws . It is also very interesting how it is actually done in these languages, which “supports” it (or at least does not prohibit it).

But I expect real programming examples, not simple metaphors.

Just to make it clear: I'm not looking for information on how to reset the previous object. Not so here.

If I am a guy who expects simultaneous merging and grouping in one column of the table - show me this, please. I know that I am smart enough to understand. :-) I just do not want to leave this question with the feeling that there is something fundamental in OOP that I will not understand.

NOTIFICATION

In the commentary in David's answer, I mentioned float, but actually referred to real numbers (a mathematical term). This is because of my rather imperfect English. Please stop collecting it .:]

THANKS

I want to thank everyone for their answers. I decide to give generosity to Steve, but this does not mean that I consider the issue closed. I'm still looking for arguments against automatically converting objects. I owe the administrator that I am a little disappointed that those who warned me the most could not give clear examples related to conversion (and not related to casting).

From my own research, I decided to mention a few things in a separate answer (in case someone might find them useful). Feel free to edit and expand the list.

+4
source share
9 answers

This is a detail of the implementation of a particular language, and not an integral limitation of object-oriented thinking. I can imagine many scenarios where this behavior would be desirable - perhaps Shape is a rectangle, but we decided to change it to Square (because a square is a type of rectangle). As you already noted, doing such things in classic OO languages, such as Java or C #, is painful.

The language design deals with compromises, and this feature has not made a cut in C # or Java and many other OO languages. To understand why ...

Let's look at my Rectangle => Square example for a minute. Perhaps in my new Z ## language, I prefer to internally store object instances as composite objects from which they are inherited. So my Rectangle is an instance of Shape in memory, + some properties and methods. Then the square is just an instance of the shape of the rectangle + some properties and methods. Changing a rectangle to a square is easy, because I do not need to convert anything - just put the finished rectangle when creating a square.

Languages ​​like C # and Java do not actually do this: they store instances of objects "flattened" - in C # the Square instance does not contain a Rectangle instance in memory - it just shares the Rectangle interface . Therefore, to implement this function in these languages ​​is much slower, because in fact they have to copy the entire flattened structure into new memory cells.

+1
source

No! From the fundamental description of classes, ClassA is not ClassB. The relationships you describe are not symmetrical / reflective. Think of it this way: every hammer is a tool, but not every tool is a hammer. If you need to turn the screw, you need a tool, but the hammer will not do the job. Replace the class definitions above with this analogy, and I think it’s clear on his face why the relationship doesn’t work.

+5
source

I'm from @DavidW.
But sometimes problems with inheritance can be solved by composition.

Read this Wikipedia art

Another option is to use an interface.

 public interface IProvideSomeData { string Data { get; } } public class ClassA, IProvideSomeData { public string FieldA = "Something first"; string IProvideSomeData.Data { get { return FieldA; } } } public class ClassB : ClassA, IProvideSomeData { public string FieldB = "Something second"; string IProvideSomeData.Data { get { return FieldB; } } } public class ClassC : IProvideSomeData { public string FieldC = "Something second"; string IProvideSomeData.Data { get { return FieldC; } } } public void DoSthWithObject(IProvideSomeData a); 

So DoSthWithObject is interface dependent and then much more reusable.

+1
source

I am trying to solve your problem in C #. I tried to make it agnostic of the language (without the static C # extension method) with a common one (but it can be changed using the Type and cast parameter).

For a complete automated system, a new universal class must be created and used for all class properties with the following logic: search for the inner class if exists or returning private if not (some introspection may be required).

The scenario I'm trying to answer is the one you sent first:

ClassA a = new ClassA (); ClassB b = (a like ClassB); // some kind of magic happens here

 using System; namespace test { /// <summary> /// Base class A /// </summary> public class ClassA { private string prop1; public ClassA InternalClassA { get; set; } public string Prop1 { get { if (this.InternalClassA == null) { return prop1; } else { return this.InternalClassA.Prop1; } } set { if (this.InternalClassA == null) { this.prop1 = value; } else { this.InternalClassA.Prop1 = value; } } } public T AsClass<T>() where T : ClassA { T obj = Activator.CreateInstance<T>(); obj.InternalClassA = this; return obj; } } } namespace test { /// <summary> /// Child class B /// </summary> public class ClassB : ClassA { public string Prop2 { get; set; } } } using System; namespace test { class Program { static void Main(string[] args) { // Instantiate A ClassA a = new ClassA(); a.Prop1 = "foo"; Console.WriteLine("Base a.prop1: " + a.Prop1); // a as ClassB ClassB b = a.AsClass<ClassB>(); b.Prop2 = "bar"; Console.WriteLine("Base b.prop1: " + b.Prop1); Console.WriteLine("Base b.prop2: " + b.Prop2); // Share memory b.Prop1 = "foo2"; Console.WriteLine("Modified a.prop1: " + a.Prop1); Console.WriteLine("Modified b.prop1: " + b.Prop1); Console.Read(); } } } 

Result:

 Base a.prop1: foo Base b.prop1: foo Base b.prop2: bar Modified a.prop1: foo2 Modified b.prop1: foo2 

Hope this gives you some idea of ​​what you want.

+1
source

The problem here is that what you are trying to achieve has little to do with inheritance.

Actually, let's say you have this:

 class A { int a; public A(int a){ this.a = a; } public int get(){ return a; } public String whoAmI(){ return "I am class A"; } } class B extends A { int b; public B(int a, int b){ super(a); this.b = b; } public int getA(){ return super.get(); } public int getB(){ return b; } public String whoAmI(){ return "I am class B"; } } 

So now you can do what soars:

 A a = new A(2); // Create a new instance of A 

or

 B b = new B(3,2); // Create a new instance of B 

Note that when creating a new instance of B, you must also pass a parameter to create the super A class, as if you were copying both of them at the same time. This is the meaning of inheritance, by building B, you are actually building a new element A plus some advanced features that are specific to B.

What you want to do is something more:

 A a = new A(2); B b = new B(3,a); 

Let's see what this class B looks like:

 class B { A a; int b; public B(int b, A a){ this.b = b; this.a = a; } public int getA(){ return a.get(); } public int getB(){ return b; } public String whoAmI(){ return "I am class B"; } } 

Here you see that you can get something like what you want, but in this case B is not a child of A, it is more like a wrapper, and A is just an attribute of B. You can also add a method to B, for example:

 public A a(){ return a; } 

And then just call:

 baget(); // which would be just like calling b.getA() 

See, this has nothing to do with inheritance, as the latter relates more to "When I create a child class, I actually also create the parent class alltogether and thus can use any child class as if it were a parent." ..

EDIT

Ok, I figured out an example to show in code what @David W said in his answer:

 class Tool { int age; int value; public Tool(int age, int value){ this.age = age; this.value = value; } public void use(){ this.age++; this.value--; } } class ScrewDriver extends Tool{ boolean starTip; int tipJamming = 0; public ScrewDriver(int age, int value, boolean starTip){ super(age,value); this.starTip = starTip; } public void screw(){ super.use(); this.tipJamming++; } } class Hammer extends Tool{ int weight; public Hammer(int age, int value, int weight){ super(age,value); this.weight = weight; } public void hammer(){ super.use(); } } 

So, you have a parent Tool class and two children ScrewDriver and Hammer. If what you are saying, perhaps you can write this:

 Tool screwDriver = new screwDriver(1,10,true); //simple inheritance polymorphism // Now if you could cast Tool to Hammer you could write ((Hammer)screwDriver).hammer(); 

This means that you actually use a screwdriver as a hammer, but that actually violates the whole idea of ​​inheritance, because if you can use any tool for each other, then what is the need to define their different tools? you can simply implement all the methods that you need in the Tool class, and use them if necessary depending on the context, for example:

 class Tool{ ... attributes and constructor ... pulic void use(){ this.age++; this.value--; } public void screw(){ use(); this.tipJamming++; } public void hammer(){ use(); } } 

and then

 Tool screwDriver = new Tool(...); Tool hammer = new Tool(...); screwDriver.screw(); hammer.hammer(); 
0
source

ClassB in some cases will require building information that ClassA will not provide.

To expand your example:

 class ClassA { int member; } class ClassB : ClassA { int bs_special_member; } 

There are two ways to achieve what you want to achieve that are generally accepted (at least as far as I know).

1 Dynamic movie. I assume that you know, but if this does not work, only if the reference to the base type you are already actually referring to the object of the correct type. For example, you did this:

 ClassA a = new ClassB(); ((dynamic_cast)a).bs_special_member; 

I do not know if this language is agnostic, but most of the languages ​​that I have seen have some similar implementation.

2 Constructor.

If you have an instance of ClassA , which is actually only ClassA and has no idea what should be for the value of bs_special_member , then you must specify this value when building ClassB .

I would do it like this (in C ++, I think you can do the same in almost any proper oo language):

 class ClassB : ClassA { int bs_special_member; ClassB(ClassA base) { member = base.member; // Apply default value bs_special_member = 456; } ClassB(ClassA base, int value_for_bs_special_member) { member = base.member; bs_special_member = value_for_bs_special_member; } }; 

What would you write:

 ClassA a; a.member = 672; ClassB b(a); print(b.bs_special_member); ClassB b2(a, 35); print(b2.bs_special_member); 

In most languages, the first constructor will automatically convert the conversion, the second - only for confirmation. If you cannot resolve the default, you are doing something wrong.

0
source

I don’t know how you would do something like this in C ++ (is that what you are using?), But the concepts that you probably want to read about / google for more information are called "covariance" "and “contravariance.” Recently, I have been doing a lot of Scala, which has a powerful (albeit somewhat confusing) type system that allows somewhat more complex type specifications, including method parameters that accept parents of the specified type.

For example, in Scala, I can define a method with a type parameter somewhat similar to C ++ templates, for example:

 def myFunction[T <: MyClass]( a:T ):T ={ //this method accepts an instance of any subclass of MyClass as a parameter, //and returns an object of the same type, whatever that type may be. return a } def myFunction[T >: MyClass]( a:T ):T ={ //this method accepts an instance of any *parent* class of MyClass as a parameter, //and returns an object of the same type, whatever that type may be. return a } 

You can also specify several type parameters and specify the relationship between them. For example, if I have a list of Dog objects, and I add a Cat object, I must return a list of Animal objects. In the case of a list, the return type will always be either a single type or a super-type parameter, but Scala will also allow you to specify the opposite in your own functions, if necessary.

Of course, if at compile time the function does not know which particular subclass you are using, you cannot use the methods from this subclass in this context ... but since it works in scala, client code when calling such a function can get an instance of an unspecified narrow type in as the return type, so it can continue to use subclass methods after the operation performed on the function.

It can also work in situations where, for example, if you are trying to override a method that returns a list of Animals while in the Cat class, you can parameterize your type so that the subclass returns a list of type "Cat" without breaking the contract of the parent interface. In this case, the implementation of the overriding subclass will have access to the Cat methods.

0
source

The only consistent way to achieve this is to provide an inherited constructor class that includes a base class. If you don’t do this, you will have all kinds of potential overflow problems, since the object’s fingerprint is now different from what was originally created.

Otherwise, you will get problems such as:

 ClassA { int A; } ClassB : ClassA { int B; } ClassA a1 = new ClassA(); a1.A = 1; ClassA a2 = new ClassA(); a2.A = 2; ClassB b = (ClassB)a; bB = 3; 

In this case, you can get these problems with a2.A by suddenly assuming the value 3 ...

0
source

Just a few notes about possible automatic conversion errors from my own research. Please forgive me if I am not accurate or say BS .:]

  • If we define the atomized conversion of A to B : A as a shorthand for create B and pass A as argument so B may copy all data from A into B , then:

    • it is either synthetic sugar, which practically does not control the cloning process;
    • OR (if the object should be converted into place by expanding the memory allocated for A ), it is less efficient than creating B , copying data, and discarding A
  • If B has its own constructor (either the calling super-constructor or not), then it is not clear whether and when constructor B should be called when cloning B from A

  • If B and A behave differently, these are some scenarios, and C contains a reference to A , then (after A convertion to B ) the behavior of the referenced object can change - it is surprising that C expected consistency here.

  • Some languages ​​(such as C #) allow you to retype inherited elements. It looks like this could lead to logical inconsistencies here ( int a in A not new int a in B ).

  • Such a transformation can change the state of an object from external code (and not from internal code), therefore it is against encapsulation.

  • Each object must be either A or B (and not somewhere in between), so it turns out that the transformation should be an atomic operation. This leads to the fact that the code should not work on A when it is converted to B I'm not sure if such an automatic lock is possible. (This is similar to the conditions of a race with incompletely designed objects).

Automatic conversion is a good idea, although for classes that are designed primarily as data wrappers (structures, ORM classes) and expose rather non-state methods. But this, apparently, is easily achieved by reflection (in languages ​​that support it).

0
source

All Articles