1

Is it possible to promote an instance to an instance of a derived class?

For example, say there is a base class Vehicle. Vehicle has derived classes: Boat, Automobile, Airplane, etc...

When an new instance of type Vehicle is created, the type of vehicle is unknown.
Later, it is discovered what the vehicle is, say it's a Boat. Is it possible to promote the Vehicle instance to a Boat object?

Clarification: the Vehicle instance may have contents set and the promoted object needs to have those contents persist

P.S. I'm successfully doing this now, but by-hand and in a very clunky manner. So, I'm wondering if there is a generally accepted, canonical way.

(24-Sep-2017) Added real case per question by Eric Lippert below:

*My entire C# experience is self-taught, so I don't have an academic foundation and don't know much regarding "patterns" and so forth. The ideas of a factory pattern and composite patterns sound interesting and may be a better way to approach the problem (need to study up).

My real-world case is that I automate electronic measurements using measurement equipment (peripherals) that connect to the computer using a shared-bus architecture. On any given test bench, a variety of different peripherals might be connected at the user's option. To automatically figure out what peripherals are available, I execute a home-grown discovery process that finds the bus address of each connected peripheral. Then, to communicate with the discovered peripheral of unknown type I create an instance of a base class I call Device. Device has certain fundamental I/O protocol methods and properties such as bus address, communication timeout and so on. Using Device I then query the peripheral for its identity to find out the manufacturer, model and serial number.

From that information I ascertain the type of peripheral and would like to "promote" the instance of Device to an instance of a derived class specifically for that peripheral type, such as SpectrumAnalyzer or Oscilloscope or whatever.

I think what I've unwittingly done is create a sort cross between a factory class and a base class. Once the type of peripheral is known, Device has a method that creates a new instance of a derived class and deeply copies its own properties to that derived instance.

Maybe if I re-think the solution in terms of factory pattern and not as a base class, I can improve the logic.

Interesting stuff... thanks for that.*

mroyer
  • 65
  • 6
  • i don't know what do you mean by promote but if you mean assigning then it's possible and its called contravariance – Niladri Sep 23 '17 at 15:24
  • 1
    It's worth noting that if you actually need to do this then this is probably an indication of a design problem in your code. Not *always*, but it's likely. – David Sep 23 '17 at 15:24
  • @Niladri - I don't know what 'contravariance' is -blush- – mroyer Sep 23 '17 at 15:28
  • https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/ this is the doc – Niladri Sep 23 '17 at 15:29
  • @David - the real situation is I have remote peripherals that I discover existence and create a communications protocol for. Only then can I discover the type of peripheral and create an 'promoted' object for. – mroyer Sep 23 '17 at 15:30
  • 2
    Niladri is completely incorrect; assignment of a value of type Boat to a variable of type Vehicle is called *assignment compatibility*. **Covariance** is the property of certain generic types that *genericized types preserve assignment compatbility*. `IEnumerable` is covariant because an `IEnumerable` may be used where an `IEnumerable` is required **because** Boat is compatible with Vehicle. **Contravariance** is the property of a generic type that it *reverses* the direction of assignment compatibility. – Eric Lippert Sep 23 '17 at 15:31
  • For example, `IComparable` is contravariant because a thing that can compare Vehicles can be used as a thing that can compare Boats, *because* boats are assignment compatible with vehicles. Notice that the direction of compatibility is *reversed* for comparers, and *preserved* for sequences. Hence *contra*variance for comparers and *co*variance for sequences. – Eric Lippert Sep 23 '17 at 15:33
  • In C#, conversions involving arrays, generic delegates and generic interfaces are permitted to be covariant or contravariant depending on whether the compiler can determine the conversion to be safe and efficient or not. I've written extensively on this topic; see any number of SO answers or the series of articles on my blog if this subject interests you. – Eric Lippert Sep 23 '17 at 15:36
  • 1
    I agree with the comment on my answer; your more detailed scenario sounds like a good use of a composite pattern, where the object becomes a sub-object of another object, which then defers its work to that object. – Eric Lippert Sep 24 '17 at 15:50
  • Only cost me three reputation points to learn what I need to learn.... :) that's a good bargain by any measure. Thanks to all for the help (not being sarcastic there, it's a genuine thanks). – mroyer Sep 24 '17 at 16:01

2 Answers2

7

Is it possible to promote the Vehicle object to a Boat object?

Nope. Ideally Vehicle would be an abstract base class, so that there would be no instances of just-a-Vehicle in the first place.

I'm successfully doing this now, but by-hand and in a very clunky manner. So, I'm wondering if there is a generally accepted, canonical way.

Nope.

When an new object of type "Vehicle" is created the type of vehicle is unknown

I'm finding it hard to imagine a scenario in which your program would be creating objects but not know until later what kind of object was created. Can you give a more realistic scenario? There's probably a better design pattern for what you want to represent.

the real situation is I have remote peripherals that I discover existence and create a communications protocol for. Only then can I discover the type of peripheral and create an 'promoted' object

It sounds like you're using the factory pattern in a really weird way. The factory pattern is the pattern of creating a "factory object", which knows how to build objects of a particular type. Your Vehicle base class is acting as a vehicle factory, not as a vehicle. You can't drive a car factory; it's not a vehicle.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 4
    One design pattern that screams out to solve this is Composite pattern, where you would have a `VehicleBaseComponent`, and at the time you know what type of car you need, you would create the specific type, and pass in the `VehicleBaseComponent` in. And the car itself would delegate whatever it needs to the base component. Just my 2 cents – Michal Ciechan Sep 23 '17 at 15:56
1

I thing that the right approach in this scenario is to have one factory method in base Device type which will discover attached peripherals and will return list of devices. The devices is concrete instances of derived classes, created during discover process. It's not a good design to create base objects in first pass and then convert it to derived instance in secont pass. Try to do the job using one pass only. You can use one static method for factory:

static List<Device> Discover() { return concrete instance; }