Why Doesn't 'Ref' and 'Out' Support Polymorphism

Why doesn't 'ref' and 'out' support polymorphism?

=============

UPDATE: I used this answer as the basis for this blog entry:

Why do ref and out parameters not allow type variation?

See the blog page for more commentary on this issue. Thanks for the great question.

=============

Let's suppose you have classes Animal, Mammal, Reptile, Giraffe, Turtle and Tiger, with the obvious subclassing relationships.

Now suppose you have a method void M(ref Mammal m). M can both read and write m.



Can you pass a variable of type Animal to M?

No. That variable could contain a Turtle, but M will assume that it contains only Mammals. A Turtle is not a Mammal.

Conclusion 1: ref parameters cannot be made "bigger". (There are more animals than mammals, so the variable is getting "bigger" because it can contain more things.)



Can you pass a variable of type Giraffe to M?

No. M can write to m, and M might want to write a Tiger into m. Now you've put a Tiger into a variable which is actually of type Giraffe.

Conclusion 2: ref parameters cannot be made "smaller".


Now consider N(out Mammal n).

Can you pass a variable of type Giraffe to N?

No. N can write to n, and N might want to write a Tiger.

Conclusion 3: out parameters cannot be made "smaller".



Can you pass a variable of type Animal to N?

Hmm.

Well, why not? N cannot read from n, it can only write to it, right? You write a Tiger to a variable of type Animal and you're all set, right?

Wrong. The rule is not "N can only write to n".

The rules are, briefly:

1) N has to write to n before N returns normally. (If N throws, all bets are off.)

2) N has to write something to n before it reads something from n.

That permits this sequence of events:

  • Declare a field x of type Animal.
  • Pass x as an out parameter to N.
  • N writes a Tiger into n, which is an alias for x.
  • On another thread, someone writes a Turtle into x.
  • N attempts to read the contents of n, and discovers a Turtle in what it thinks is a variable of type Mammal.

Clearly we want to make that illegal.

Conclusion 4: out parameters cannot be made "larger".


Final conclusion: Neither ref nor out parameters may vary their types. To do otherwise is to break verifiable type safety.

If these issues in basic type theory interest you, consider reading my series on how covariance and contravariance work in C# 4.0.

Why doesn't polymorphism work without pointers/references?

In C++, an object always has a fixed type and size known at compile-time and (if it can and does have its address taken) always exists at a fixed address for the duration of its lifetime. These are features inherited from C which help make both languages suitable for low-level systems programming. (All of this is subject to the as-if, rule, though: a conforming compiler is free to do whatever it pleases with code as long as it can be proven to have no detectable effect on any behavior of a conforming program that is guaranteed by the standard.)

A virtual function in C++ is defined (more or less, no need for extreme language lawyering) as executing based on the run-time type of an object; when called directly on an object this will always be the compile-time type of the object, so there is no polymorphism when a virtual function is called this way.

Note that this didn't necessarily have to be the case: object types with virtual functions are usually implemented in C++ with a per-object pointer to a table of virtual functions which is unique to each type. If so inclined, a compiler for some hypothetical variant of C++ could implement assignment on objects (such as Base b; b = Derived()) as copying both the contents of the object and the virtual table pointer along with it, which would easily work if both Base and Derived were the same size. In the case that the two were not the same size, the compiler could even insert code that pauses the program for an arbitrary amount of time in order to rearrange memory in the program and update all possible references to that memory in a way that could be proven to have no detectable effect on the semantics of the program, terminating the program if no such rearrangement could be found: this would be very inefficient, though, and could not be guaranteed to ever halt, obviously not desirable features for an assignment operator to have.

So in lieu of the above, polymorphism in C++ is accomplished by allowing references and pointers to objects to reference and point to objects of their declared compile-time types and any subtypes thereof. When a virtual function is called through a reference or pointer, and the compiler cannot prove that the object referenced or pointed to is of a run-time type with a specific known implementation of that virtual function, the compiler inserts code which looks up the correct virtual function to call a run-time. It did not have to be this way, either: references and pointers could have been defined as being non-polymorphic (disallowing them to reference or point to subtypes of their declared types) and forcing the programmer to come up with alternative ways of implementing polymorphism. The latter is clearly possible since it's done all the time in C, but at that point there's not much reason to have a new language at all.

In sum, the semantics of C++ are designed in such a way to allow the high-level abstraction and encapsulation of object-oriented polymorphism while still retaining features (like low-level access and explicit management of memory) which allow it to be suitable for low-level development. You could easily design a language that had some other semantics, but it would not be C++ and would have different benefits and drawbacks.

Why ref parameters can not be ignored like out parameters?

The reason is simple: because you're not allowed to pass an uninitialized variable into a ref parameter. This has always been the case, and the new syntactical sugar in C#7 doesn't change that.

Observe:

int i;
MyOutParameterMethod(out i); // allowed

int j;
MyRefParameterMethod(ref j); // compile error

The new feature in C#7 allows you to create a variable in the process of calling a method with an out parameter. It doesn't change the rules about uninitialized variables. The purpose of a ref parameter is to allow passing an already-initialized value into a method and (optionally) allow the original variable to be changed. The compiler semantics inside the method body treat ref parameters as initialized variables and out parameters as uninitialized variables. And it remains that way in C#7.

Why polymorphism doesn't work on operators via object types?

First of all let's look at the Object class itself:

Sample Image

As you can see the Equals method has virtual key on it, meaning that in any class that inherits from object, if you override that method, then even if your variable object o2 = a2; is typeof object pointing to new instance of A class -> this method will be invoked by the runtime:

public override bool Equals(object other)

On the other hand the operator == is not virtual, it cannot be overriden, so when referring to object variable it will always resolve to System.Object.ReferenceEquals

Why are the switch-case part and runtime polymorphism is not working?

you never populated your farmers1 list, it is always empty therefore isFruitAvailable is always false. Also, your while loop never terminates, it just keeps spinning ("break" statements that you have are inside switch and have nothing to do with "while")

for(Farmer fruit : farmers1) {
if(fruit.getName().equals(tempName)) {
farmers1.add(fruit); //the only place where you add something, but it never gets executed
break;
}
}

C# ref Polymorphism workaround

Generics perhaps?

private void customShow<T>(ref T someForm) where T : Form, new()
{
if (someForm == null || someForm.IsDisposed) someForm = new T();
someForm.StartPosition = FormStartPosition.CenterScreen;
someForm.MdiParent = this;
someForm.Show();
someForm.WindowState = FormWindowState.Maximized;
}

And then I wanted to do this:

private void mnuKategori_Click(object sender, EventArgs e)
{
customShow(ref frmKategori);
frmKategori.isCRUD = true;
}

Problem with string as reference parameter when method takes Object C#

This is a duplicate of

Why doesn't 'ref' and 'out' support polymorphism?

See that answer for why it doesn't work.

To make it work, you could make a generic method:

public static void Swap<T>(ref T a, ref T b) 
{
T t = b;
b = a;
a = t;
}

And now all the types check out.

Why doesn't returning by ref work for elements of collections?

The answer is in that same link you posted:

You can only return refs that are “safe to return”: Ones that were
passed to you, and ones that point into fields in objects.

Your example satisfies neither. You are creating the list inside the function (so the object will go out of scope and its pointer will be invalid), and it doesn't point to a field of an object.



Related Topics



Leave a reply



Submit