As I mentioned in the comments, C# is strongly typed. Instead of creating an array of objects (into which any object of any type can be added), instead, create an array of cars:
Car[] cars = new Car[5];
Now, your array can contain nothing but Car objects (or instances of Car sub-classes). No one can say cars[2] = new Horse("Secretariat"); (something that can be done with a object[] array.
You can also use var instead:
var cars = new Car[5];
That way, you don't have to type the type specification twice. The two statements have completely the same meaning. You can only use var when the type of the right-hand side of the assignment can be deduced by the compiler from context. That type is applied to the variable on the left-hand side.
You can also initialize an array when you create it:
var cars = new Car[] {
porsche,
mercedes,
bmw,
punto,
ferrari,
};
When you do that, you don't have to include the array size in the array constructor expression (you can, but you don't have to).
You can also do this:
var cars = new Car[] {
new Car("Porshe", 340),
new Car("Mercedes", 320),
new Car("BMW", 330),
new Car("Punto", 220),
new Car("Ferrari", 380),
};
You don't use the names porshe, mercedes, etc. other than for the array initialization, so you don't really need them. You can use them (and it likely won't cost any more at runtime, but...)
As @crowcoder has pointed out, you really should add a ToString method:
public override string ToString()
{
return Model;
}
That will make your code behave the way you expect. The Console.WriteLine method, when it's passed a string will output a string. However, if it is passed an object of any other type, it will call ToString on the object. The object class declares ToString as a virtual or overridable method. The object class includes an implementation of ToString, but the only thing it knows about an object is the type of the object, so that is what it uses as the returned string. Since the method is virtual/overridable, sub-classes of object (i.e., just about every type) can override that method and do whatever they'd like. Your expectation was that the car's Model property would be output, so that's what this does.
Other folks are pushing you away from arrays and towards List. There are good reasons to do this. One of them (familiar to Javascript programmers) is that List is stretchy; it will grow as you add things (like a Javascript array does). C# arrays are necessarily fixed size.
One other note. As you found out when you had an array of objects that contained Cars, C#'s type-safety will prevent you from directly calling Car-specific methods on objects that the compiler knows only as objects. In C#, a variable types as object can refer to anything, but it only has a very small number of methods (like ToString) that can be called on it directly (unlike Javascript, where member binding is done at runtime rather than compile time).
If you want to get Javascript-like variable behavior, you can use dynamic as the type (var cars = new dynamic[]{bmw, etc.}). Variables typed dynamic can behave very much like Javascript variables (and there are types that behave nearly exactly like Javascript objects (they derive from Expando)).
If you have an expression like (like you showed): Console.WriteLine($"some {car.Model}");, and car is typed dynamic, then, at runtime, the type of car is examined to determine if it has a Model property. If it does, it is evaluated. If it doesn't, it throws an exception. All this is done at runtime. They error that you saw (CS1061:'object' does not contain a definition for 'Model') happens at compile time. Because of type-safety, a Car is known to have a Model property and no-runtime check needs to be done.
That said, nearly everyone monitoring the C# tag will tell you dont' use dynamic unless you have a really good reason to. C#'s type-safety is its big advantage (and once you get used to it, it just makes sense).
var cars = new Car[5];). You can also initialize an array when you construct it:var cars = new Car[]{porsche, mercedes, bmw, punto, ferrari};. Note the array size gets set from the number of objects in the array initialization that way. As @Crowcoder notes, make sure to overrideToStringin yourCarclass:public override string ToString() {return model;}Console.WriteLine($"some {car.Model}");Car[] cars = new Car[5];