Change parameter type when implementing an abstrac

2019-04-09 17:49发布

Is there some way to define an abstract type as a parameter in an abstract method, and when that method is implemented in a derived class, you change the type of the method to accept a derived type?

Code:

public abstract class ProductBase
{
}

public class SomeProduct
    : ProductBase
{

}

public abstract class A
{
    protected abstract void addProduct(ProductBase p);
}

// This works
public class B : A
{        
    protected override void addProduct(ProductBase p)
    {
        // Do some work
    }
}

// This is what I'd like to do
public class C : A
{
    // Compiler error here because I have accepted a SomeProduct and not a ProductBase
    protected override void addProduct(SomeProduct p)
    {
        // Do some work on the specialisation - I can use the SomeProduct directly
    }
}

In my head, it makes some kind of sense. An abstract class indicating there is a method which derived classes must implement, but they can change the type of object passed in as a parameter, so long as it is from the same inheritance chain...

What I have ended up doing, is to remove the abstract method AddProduct from the abstract class, and instead just implementing it in the derived class anyway, but there's then no contract for other classes in the future that they must create their own implementation of AddProduct. And it doesn't feel right.

I hope this makes sense. Apologies if this is a duplicate question but I couldn't find anything by searching.

Thanks,
bgs264

3条回答
Deceive 欺骗
2楼-- · 2019-04-09 18:22

You can make your A class to generic and use the generic argument in your addProduct method:

public abstract class A<TProduct> where TProduct : ProductBase 
{
    protected abstract void addProduct(TProduct p);
}

public class B : A<ProductBase>
{        
    protected override void addProduct(ProductBase p)
    {
    }
}

public class C : A<SomeProduct>
{
    protected override void addProduct(SomeProduct p)
    {
    }
}
查看更多
我欲成王,谁敢阻挡
3楼-- · 2019-04-09 18:29

No, that is not possible: Assume you are calling addProduct on a variable of class A that points to an instance of class C, passing an object that is a ProductBase, but not a SomeProduct. The passed instance could not be converted to SomeProduct, which is why this will not compile in the first place.

The closest thing to this is a solution with generics:

public abstract class A<T>
    where T : ProductBase
{
    protected abstract void addProduct(T p);

}

Then, you can define your class C like so:

public class C : A<SomeProduct>
{
    protected override void addProduct(SomeProduct p);
}

Of course, that means you have to type any variables of type A to SomeProduct, as in A<SomeProduct> = new C();.

You cannot have just a variable of type A (that provides an addProduct method) any more without specifying the actual value of the T type argument. On the other hand, calling that addProduct method would not have been possible in your solution, either, as outlined above.

You might introduce a non-strongly-typed method now:

public abstract class A<T>
    where T : ProductBase
{
    protected abstract void addProduct(T p);

    protected void addProductUntyped(ProductBase p)
    {
        T typedProduct = p as ProductBase;
        if (typedProduct != null) {
            addProduct(typedProduct);
        } else {
            throw new ArgumentException("p has an incompatible type.");
        }
    }
}

However, such a solution is usually really only required when there are some users of the class that can know the generic type while others don't.

查看更多
等我变得足够好
4楼-- · 2019-04-09 18:36

Frankly, for implementation code I'd just live with a cast. The only time I'd make this "pretty" is if it impacted public callers:

protected override void addProduct(ProductBase p)
{
    SomeProduct prod = (SomeProduct)p;
    // ...
}

One option, that I don't like much myself, is generics:

public abstract class A<T> where T : ProductBase
{
    protected abstract void addProduct(T p);
}

public class C : A<SomeProduct>
{
    protected override void addProduct(SomeProduct p)
    {
        // ...
    }
}

The reason I don't like this is that you can't just use the non-generic A anymore, so you lose a lot of abstraction. Another option is to new the method to re-declare the parameters. Unfortunately, you can't new and override at the same level, but a workaround is to use 2 methods:

public abstract class A
{
    protected void addProduct(ProductBase p) { addProductImpl(p);}
    protected abstract void addProductImpl(ProductBase p);
}

public class C : A
{
    new protected void addProduct(SomeProduct p) { addProductImpl(p);}
    protected override void addProductImpl(ProductBase p)
    {
        SomeProduct prod = (SomeProduct) p;
        // ...
    }
}
查看更多
登录 后发表回答