0

Here's my code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Rextester
{
    public class Program
    {
        public static void Main(string[] args)
        {
            ISampleInterface a = new A();
            ISampleInterface b = new B();

            a.SampleMethod();
            b.SampleMethod();

            //Console.WriteLine(a.myValue); // can't do it!! a is not A
            Console.WriteLine(a.GetType()); // uhm...
        }
    }

    interface ISampleInterface
    {
        void SampleMethod();
    }

    class A : ISampleInterface
    {
        public double myValue = 10.0;

        public void SampleMethod() {
            Console.WriteLine("A");
        }
    }

    class B : ISampleInterface
    {
        public double myValue = 20.0;

        public void SampleMethod() {
            Console.WriteLine("B");
        }
    }
}

I init a class by interface (implemented by the class).

Obviously, can't access to a.myValue, because correctly Rextester.ISampleInterface doesn't contain such a definition.

But if I ask to compiler which type is a, it outputs Rextester.A (which is not, I believe).

Why? And more important, which kind of class is a? A sort of hybrid-sliced class limited by its interface? Not sure how I would define it...

markzzz
  • 47,390
  • 120
  • 299
  • 507

3 Answers3

2

What methods you can call (outside reflection/dynamic) are based on the compile-time type of the variable.

Reference casts (which is what casting an existing reference to an interface it implements is) don't change the type of the object at all. But if you're storing the result of that cast in a variable, the type of the variable is what matters.

There's nothing comparable to C++'s slicing (what I presume you're referring to by slicing) in the .NET world, that I can think of - certainly you wouldn't get it from writing straightforward code.

A variable that is declared to be of an interface type will never contain a reference to something that is "just" that interface. It'll always be an object of some specific concrete struct/class type.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • Are you saying that in memory, it will allocate space for a `A`? Even if part of it can't be accessed? – markzzz Feb 28 '19 at 14:52
  • 1
    You called `new A()` - that's always going to produce an `A`-sized object. Thereafter, there's only that one object that multiple references can reference. And you can access the other members by casting back to `A`. – Damien_The_Unbeliever Feb 28 '19 at 14:53
  • @markzzz Remember that classes and interfaces are _reference types_ by default in C#, meaning that the variable `a` is a _reference_ to an object in memory, not the object itself, so it can reference an `A` or a `B`. It would be analogous to `ISampleInterface*` in C++. – D Stanley Feb 28 '19 at 15:01
  • I would say `ISampleInterface&` :) Yeah, I come mostly from C++, I follow this hardly... :) – markzzz Feb 28 '19 at 15:05
  • No, I literally mean `ISampleInterface*`. It's a _reference_ ("pointer" in C++) to an object. – D Stanley Feb 28 '19 at 15:10
1

But if I ask to compiler which type is a, it outputs Rextester.A (which is not, I believe)

If by "asking the compiler" you mean calling a.GetType(), that's not what you're doing. You're asking the runtime for the actual type of the object that a references. The runtime correctly tells you that it is a Rextester.A.

The compiler on the other hand, does not know this. The variable is declared as an ISampleInterface, so all the compiler can safely do is bind to methods defined by the interface.

If you cast the variable to an A, then you can access its members:

A newA = a;
newA.myValue = 15;  // perfectly valid

Note that the actual object that a references has not changed (meaning it's the same object - obviously you changed one of its member fields). You can still reference is as the interface through a or the class through newA.

If you had asked the compiler what type a is but using a method like this:

public Type GetCompileTimeType<T>(T inputObject)
{
    return typeof(T);
}

(credit to this answer)

and calling

Console.WriteLine(GetCompileTimeType(a));

you'd get Rextester.ISampleInterface

D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • "The runtime correctly tells you that it is a Rextester.A" Not sure about this. Why? At runtime its still not a `A` at all. Can't access to the members of it, since they are out of the `ISampleInterface` scope. – markzzz Feb 28 '19 at 15:02
  • @markzzz - yes it is. These casts are what Eric Lippert refers to as [*representation-preserving conversions*](https://ericlippert.com/2009/03/03/representation-and-identity/). I think in C++ it'd be something similar to `reinterpret_cast<>` - you're not changing the bits/bytes, you're not performing some conversion routine. It's still the "same thing". `ReferenceEquals((A)a, a)` returns `true`. – Damien_The_Unbeliever Feb 28 '19 at 15:08
  • Casting does not change the underlying object at all. Casts on value types will give you a _new_ object of the requested type - casts on _reference types_ will give you a different reference to the same object. I too came from a C++ background so understanding reference types can be tricky. Just realize that C# in not an _extension_ of C++ so some of the syntax can be confusing if you try and translate it to C++. – D Stanley Feb 28 '19 at 15:13
  • 1
    "casts on reference types will give you a different reference to the same object" - only, if, again the cast is one of those Eric refers to as representation-preserving conversions. Casts which end up calling a conversion operator will (normally) not be returning the same object. – Damien_The_Unbeliever Feb 28 '19 at 15:17
  • @Damien_The_Unbeliever fair enough, there are exceptions. My main point is that casting a reference does not change the actual object. – D Stanley Feb 28 '19 at 15:24
0

a.myValue cannot be accessed because it's in scope as ISampleInterface

a.GetType() correctly results in A because this is resolved at runtime to the instance of the type assigned to a, which is A. It's only in scope as ISampleInterface.

For example...

ISampleInterface a = new A();

You are creating an instance of A declared as ISampleInterface.

a.SampleMethod();

You can access things that are declared.

Console.WriteLine(a.myValue);

You can't access things that are not declared.

Console.WriteLine(a.GetType());

GetType is callable from System.Object which everything naturally extends, so you can call this, because it's declared from an implicit base type. In this context, GetType returns A because that's exactly what a is; an instance of A.

Matthew Layton
  • 39,871
  • 52
  • 185
  • 313
  • 2
    I think using `scope` instead of `type` here is pretty confusing given the existing meaning of `scope` in C#. – Lee Feb 28 '19 at 15:16