简体   繁体   中英

Abstract base class which calls derived method in constructor?

I'm trying to code DRY, and I have the following setup, which Visual Studio's Code Analysis system is telling me is unwise:

public abstract class ShaderBase
{

    protected ShaderBase(Device device, string vertexShaderString, string pixelShaderString)
    {
        ShaderSignature inputSignature;
        using (ShaderBytecode bytecode = ShaderBytecode.CompileFromFile(vertexShaderString, "VShader", "vs_4_0", ShaderFlags.None, EffectFlags.None))
        {
            vertexShader = new VertexShader(device, bytecode);
            inputSignature = ShaderSignature.GetInputSignature(bytecode);
        }

        inputLayout = MakeInputLayout(device,inputSignature);
    }

    protected abstract InputLayout MakeInputLayout(Device device, ShaderSignature inputSignature);
}

public class TextureShader:ShaderBase
{
    public ColourShader(Device device) : base(device,"shaders/colour.fx", "shaders/colour.fx")
    {
    }

    protected override InputLayout MakeInputLayout(Device device, SlimDX.D3DCompiler.ShaderSignature inputSignature)
    {
        return new InputLayout(device, inputSignature, new[] { 
            new InputElement("POSITION", 0, SlimDX.DXGI.Format.R32G32B32_Float, 0), 
            new InputElement("COLOR",0,SlimDX.DXGI.Format.R32G32B32_Float,0)
        });
    }
}

So as you can see, I have a base class, from which I have multiple derived classes. Each derived class uses a different InputLayout and must therefore use a different implementation of MakeInputLayout, which is why I override it. However, every derived class must execute the code I've put in the base class's constructor, including the call to whichever implementation of MakeInputLayout that derived class has.

I'm trying to avoid code duplication where possible, but Microsoft is suggesting I should never be calling overrideable functions in base class constructors, even though none of the overriden implementations ever depend on values set up at runtime (they could, technically, have been labelled static, if c# allowed us to override statics).

What I want to know is, what's the acceptable way of having a base class force derived classes to call their own derived implementation of a function in their constructors? Or am I just going to have to copy and past some code around and reduce the maintainability of the system?

  1. You can ignore Visual Studio's Code Analysis, if you 100% sure that it will not be a problem in your case. However, it might be not very safety and personally I do not recomend to do so.

  2. Use helper interfaces/classes/delegates to avoid constructor virtual method call:

     public interface IInputLayoutMaker { InputLayout MakeInputLayout(Device device, SlimDX.D3DCompiler.ShaderSignature inputSignature); } public abstract class ShaderBase { protected ShaderBase(Device device, string vertexShaderString, string pixelShaderString, IInputLayoutMaker inputLayoutMaker) { ShaderSignature inputSignature; using (ShaderBytecode bytecode = ShaderBytecode.CompileFromFile(vertexShaderString, "VShader", "vs_4_0", ShaderFlags.None, EffectFlags.None)) { vertexShader = new VertexShader(device, bytecode); inputSignature = ShaderSignature.GetInputSignature(bytecode); } inputLayout = inputLayoutMaker.MakeInputLayout(device,inputSignature); } protected abstract InputLayout MakeInputLayout(Device device, ShaderSignature inputSignature); } public class TextureShader:ShaderBase { private class TextureShaderInputLayoutMaker : IInputLayoutMaker { public InputLayout MakeInputLayout(Device device, SlimDX.D3DCompiler.ShaderSignature inputSignature) { return new InputLayout(device, inputSignature, new[] { new InputElement("POSITION", 0, SlimDX.DXGI.Format.R32G32B32_Float, 0), new InputElement("COLOR",0,SlimDX.DXGI.Format.R32G32B32_Float,0) }); } } public ColourShader(Device device) : base(device,"shaders/colour.fx", "shaders/colour.fx", new TextureShaderInputLayoutMaker()) { } } 

You don't have to copy anything around. As you said, those methods could be static. So make them and pass the result to the base class constructor.

The current code you have doesn't make that all to simple, because MakeInputLayout depends on a value created in the constructor of the base class. While you could somehow extract that as well, I think this would be getting messy.

Therefore, I propose a different approach:

Create an IInputLayoutProvider interface along with its implementations and pass that to the base class.

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