marsh-wiggle
marsh-wiggle

Reputation: 2803

How to implement implicitly casts?

I see that some properties allow beeing casted implicitly. Like DataTable.Rows cann be castet in DataRow and in DateRowView.

void DemoFunc()
{
    DataTable dtTable = new DataTable();

    foreach (DataRow row in dtTable.Rows)
    {


    }

    foreach (DataRowView row in dtTable.Rows)
    {

    }
}

I could use this behaviour in my own code. Does anybody know a simple example to understand how it works?

Edit:

What is my goal? I wonder if its possible to design code like that:

Void DemoFunc2()
{

MyObject myObjec = new MyObject()

OtherObject otherObject = myObject.Property;
DifferentObject differentObject = myObject.Property;

}

Can a property return diffent object-types?

Edit 2:

In this question are mixed two different things. Once the wondering about how to get a property with two different return types, based on the missunderstandig on how DataTable.Rows is designed.

Upvotes: 0

Views: 83

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1063198

Simple: that doesn't work:

Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Data.DataRow' to type 'System.Data.DataRowView'.

However, foreach works on a pattern that allows, in some cases, implicit casts. Whether or not they work. In this example, it cannot be checked by the compiler because .Rows exposes an object-based iterator (in this case, IEnumerable/IEnumerator, but even that is not strictly necessary).

More generally, there is the implicit conversion operator, but that still wouldn't apply with an object-based iterator, because operators are applied against statically known types only (unless you use dynamic).

In the scenario where the GetEnumerator() method allows access to something more than object, the compiler can tell us about this:

foreach (DataRowView row in dtTable.AsEnumerable())
{

}

to which the compiler adds an error:

Error 1 Cannot convert type 'System.Data.DataRow' to 'System.Data.DataRowView'

Although in this scenario, it would at least allow implicit operators to be used, if defined. Which they aren't.


More detail:

Why does it compile?

.Rows is typed as DataRowCollection. Since this dates back to .NET 1.1, it implements ICollection and IEnumerable, but it was never updated to implement IEnumerable<DataRow>. Likewise, the GetEnumerator() method returns IEnumerator - not something more specific.

The problem with IEnumerator (as different to IEnumerator<T>) is that the .Current is typed only as object, because .NET 1.1 and C# 1.2 didn't have generics. It would have been very awkward if you had to do:

foreach(object tmp in table.Rows)
{
    DataRowView row = (DataRowView)tmp; // this is our known-bad code
    // ...

so for convenience, the language allows you to do that implicitly, i.e. the above is roughly identical to:

foreach(DataRowView row in table.Rows) // this is our known-bad code
{
    // ...

What this actually becomes is something like:

{
    IEnumerator iter = table.Rows.GetEnumerator();
    // note this ^^^ is disposed after the loop if IDisposable; not shown
    while(iter.MoveNext())
    {
        DataRowView row = (DataRowView) iter.Current; // .Current is: object
        // ...
    }
}

Notice how there is a cast from object to DataRowView? If the thing returned from .Current is not actually a DataRowView (and: it isn't), that will only fail at runtime, when the cast is performed. If we do the same thing with DataRow instead of DataRowView, it would have succeeded.

Note that the foreach pattern doesn't actually depend on IEnumerator; it is also possible for any type to have a GetEnumerator() method, and return a custom iterator type that has a stronger (typed) .Current implementation. If DataRowCollection had done this, **your code with DataRowView would fail. This could be a completely custom iterator, or could be simply IEnumerable<DataRow> / IEnumerator<DataRow>. Consider the entirely hypothetical:

{
    IEnumerator<DataRow> iter = table.Rows.GetEnumerator();
    // note this ^^^ is disposed after the loop if IDisposable; not shown
    while(iter.MoveNext())
    {
        DataRowView row = (DataRowView) iter.Current; // .Current is: DataRow
        // ...
    }
}

Now with this construct, we can see that we are casting from DataRow to DataRowView, and that is something that the compiler can check at compile-time. Since they are classes, it will fail unless there is either an inheritance relationship, or a custom conversion operator. Since neither exists: it will fail.

Upvotes: 3

Related Questions