简体   繁体   中英

Dynamic Casting C#

I have Parent class X, and Parent class H.

H has in its field data a reference to a type X. H's constructor requires an instance of type X, which is then stored in the reference.

I then derive from X a Child class x1, and from H a child class h1.

h1 will accept in its constructor an instance of X or of x1.

But the methods and properties of x1 which are not already defined in its parent class X will not be available to h1.

How can I permanently cast x1 to the type of x1 within my class h1?

Let's start by rewriting the question into something comprehensible. Instead of X and H, let's call them Food and Animal. Your scenario is:

abstract class Animal
{
    protected Food favourite;
    protected Animal(Food f) { this.favourite = f; }
}
abstract class Food
{
}
sealed class Banana : Food 
{
    public void Peel() {}
}
sealed class Monkey : Animal
{
    public Monkey(Banana banana) : base(banana) {}
    public PeelMyBanana()
    {
        this.favourite.Peel(); // Error, favourite is of type Food, not Banana
    }
}

And your question is:

How can I permanently make 'favourite' known to be of type 'Banana' inside 'Monkey'?

The short answer is: you can't. The field is of the given type and can contain any reference to an instance of type Food. The compiler and runtime do not know or care that you happen to know of a stronger restriction on what types of things will in fact be assigned to the field.

There are a number of ways you could solve this problem. You could add an accessor that does the conversion for you:

sealed class Monkey : Animal
{
    public Monkey(Banana banana) : base(banana) {}

    private Banana Favourite { get { return (Banana)this.favourite; } }        

    public PeelMyBanana()
    {
        this.Favourite.Peel(); // Works
    }
}

Or you could genericize the base class:

abstract class Animal<F> where F : Food
{
    protected F favourite;
    protected Animal(F f) { this.favourite = f; }
}
abstract class Food
{
}
sealed class Banana : Food 
{
    public void Peel() {}
}
sealed class Monkey : Animal<Banana>
{
    public Monkey(Banana banana) : base(banana) {}
    public PeelMyBanana()
    {
        this.favourite.Peel(); // Legal; Animal<Banana>.favourite is of type Banana
    }
}

There is no such thing as a "permanent" cast (or, rather, the idea of a "permanent cast" is nonsensical, as that implies that casting does something to the object , which it does not, so there is nothing to either persist or undo). Your variable is of type H , and the type of the variable will never change. Your instance is of type H1 , and the instance's type will never change. Casting simply tells the compiler "Yes, even though I am only referencing this instance as type H , I actually know that it is of type H1 , so it's safe to store it in any variable that can reference an instance of H1 ."

If your structure looks like this:

class H
{

}

class X
{
    public H HValue { get; private set; }

    public X(H h)
    {
        HValue = h;
    }
}

class H1 : H
{
    public void Foo() { }
}

class X1 : X
{
    public X1(H1 h1) : base(h1)
    {

    }
}

Then you're going to have to store the value for the h1 variable somewhere else if you always want to use that value without downcasting.

Now, a somewhat "smelly" way of accomplishing what you want (not having to write the casting code every time), you could do this:

class X1 : X
{
    public X1(H1 h) : base(h) { }

    public new H1 HValue 
    { 
        get { return (H1)base.HValue; }
    }
}

This would allow you to refer to the same property named HValue anywhere you had a reference to X1 in a variable of type X1 . So...

X x = new X1(new H1());  // x.HValue would be of type H, even though the 
                         // reference itself is H1
X1 x = new X1(new H1()); // x.HValue would be of type H1

If I'm understanding correctly, you have the following classes.

class X
{
}

class X1 : X
{
}

class H
{
    H(X x)
    {
        MyX = x;
    }

    X MyX { get; private set; }
}

class H1 : H
{
    H1(X x) : base(x)
    {
    }
}

You want H1 to deal with MyX as though it is the more specific X1 type.

The simplest way to do this is to have another property on H1 that references x as X1:

class H1 : H
{
    H1(X1 x) : base(x)
    {
        MyX1 = x;
    }

    X1 MyX1 { get; private set; }
}

By creating a constructor on H1 that accepts only an X1 (and not any old X), you're ensuring that it will always be set correctly.

That's what generics are for. If I understand correctly what you are asking, your H class should be generic on some "C", (class H<C>) where C derives from X, so that H can be written to work with X, but then H1 can be defined as deriving from H<X1> so that it can work with X1.

EDIT: Of course, depending on how complicated these classes are, it might not be worth the hassle of making H generic. In that case, I would go with Matt Dillard's answer.

I don't think you can.

You could add a field of type x1 to h1 and set it if you are given an x1 in h1's constructor, then use this field to access the methods of x1 in h1 you want to access (remembering that x1 field might not be set).

Its not clear from your description if h1 constructor looks like:

h1(X xInstance)

or

h1(X xinstance, x1 x1Instance)

but I'm assuming the first, even though you say 'h1 will accept in its constructor an instance of X and of x1.' as otherwise you know you have an x1 and so it becomes fairly straightforward :)

so I think you want to do this:

public class H1: H
{
    private X m_x;
    private X1 m_x1;

    public H1(X x):base(x)
    {
        m_x=x;
        X1 x1 = x as X1;
        if (x1!=null)
            m_x1=x1;
    }
}

您可以使用Interface,所以H没有对X的引用,而对IX的引用,则Xx1实现IX方法/属性。

If you are targeting .Net 4.0 then you can use the new dynamic type described here . It will functionally allow you to do what you want, but from a design standpoint it slaps in the face the concept of strong type checking at compile time.

To me, this sounds like a dodgy design. Sounds like you have got the problem "Parallel Class Heirarchies".

Without more detail I can't give more specific advice. One design pattern that might be of help to you is called "Bridge". However, loads of way smarter people than me have written good general advice, so I'm going to point you at their books:

Martin Fowler - Refactoring Joshua Kerievsky - Refactoring to Patterns Kent Beck - Implementation Patterns (gives a good example solving this problem by re-considering your abstractions).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM