2011-07-22 149 views
28

正如您在下面的代码中看到的,DoStuff()方法在构造一个Child对象期间在Init()之前被调用。C#如何在对象构造后执行代码(后构造)

我现在处于一个有许多孩子班的情况。因此,在每个子的构造函数中直接在Init()之后重复调用DoStuff()方法并不是一个优雅的解决方案。

有没有什么办法可以在父类中创建某种类型的构造函数,并在子构造函数之后执行?这样,我可以在那里调用DoStuff()方法。

如果您有任何其他的设计理念可以解决我的问题,我也想听听!

abstract class Parent 
{ 
    public Parent() 
    { 
     DoStuff(); 
    } 

    protected abstract void DoStuff(); 
} 

class Child : Parent 
{ 
    public Child() 
    // DoStuff is called here before Init 
    // because of the preconstruction 
    { 
     Init(); 
    } 

    private void Init() 
    { 
     // needs to be called before doing stuff 
    } 

    protected override void DoStuff() 
    { 
     // stuff 
    } 
} 
+0

找到显示构建顺序的链接... http://www.csharp411.com/c-object-initialization/基本上指出您的派生实例构造函数是要做的东西的最后一个。我认为将DoStuff从基类构造函数中提取出来并明确地调用它是最直接的方法,或者提供可以被覆盖的基类Init。 – deepee1

+0

+1:我不知道为什么,但我总是(错误地)认为子构造函数首先被调用;这就是为什么我总是在我的构造函数调用中使用base()。谢谢你教育我。 – NotMe

回答

7

如何:

abstract class Parent 
{ 
    public Parent() 
    { 
     Init(); 
     DoStuff(); 
    } 

    protected abstract void DoStuff(); 
    protected abstract void Init(); 
} 

class Child : Parent 
{ 
    public Child() 
    { 
    } 

    protected override void Init() 
    { 
     // needs to be called before doing stuff 
    } 

    protected override void DoStuff() 
    { 
     // stuff 
    } 
} 
+0

我会在Parent的'DoStuff'调用'Init()'之前调用'Init()',因为基类仍然没有意识到需要执行初始化操作(除非所有子类最终结束)要求它......) –

+0

但父母的DoStuff是抽象的,实际上并没有一种方法。 DoStuff和Init都在孩子身上。我同意,如果有些孩子不需要调用Init,那么这个解决方案会变得很麻烦。 – taylonr

+2

-1您是否看到类层次结构和构造函数初始化本身包含当前实现中的DoStuff()和Init()?在这种情况下,这是无用的代码片段 –

14

如果您对您构建一个对象复杂的逻辑,然后再考虑FactoryMethod模式。

在你的情况我会实现它作为一个简单的

public static Parent Construct(someParam) 

方法需要一些参数,并在此基础上决定实例化哪个子类。 您可以从构造函数中删除DoStuff()方法调用,并在新实例的Construct()中调用它。

此外,您应该避免在构造函数中调用虚拟/抽象方法。看到这个问题的更多细节:Virtual member call in a constructor

1

更正:每this answer作为,你不能确定建设的子类时的基类的构造函数被调用时。

E.g.这不起作用:

所以,是的,正如其他人所指出的,如果事件的顺序很重要,那么基类需要能够通过声明,以抽象的方式,或以适应(更好的)通过让子类实现DoStuff代表事件序列:

protected override void DoStuff() 
{ 
    Init(); 
    base.DoStuff(); 
} 
+0

In * C#*?我知道你可以在C++中做这些事情。 –

+0

-1 - 构造函数体中的base()不会编译 –

+0

通过替代方法确认并更正。 –

1

DoStuff是抽象的。只需调用DoStuff顶部的Init即可。

+0

对于我的场景,需要在每个子项的DoStuff()重写中调用Init(),这并不比在每个子项的构造函数中放置DoStuff()更好。另外,Init()可能需要用于其他方法,因此需要在构建中的某个地方完成。 – asmo

+1

这使得泰勒的解决方案更加可行。 –

+0

+1他和你的都很好。 – hoodaticus

0

而不是使用abstract方法,它会要求你实现所有子类的方法,你可以尝试:

public class Parent 
{ 
    public Parent() 
    { 
     PostConstructor(); 
    } 


    protected virtual void PostConstructor() 
    { 
    } 
} 

public class Child : Parent 
{ 
    protected override void PostConstructor() 
    { 
     base.PostConstructor(); 

     /// do whatever initialization here that you require 
    } 
} 

public class ChildWithoutOverride 
{ 
    /// not necessary to override PostConstructor 
} 
+0

K.I.S.S.你;) –

+0

@Artur?这很简单。 –

+0

是的+1优雅,我是K.I.S.S. apologet。否则即使很小的框架也很容易变得怪异。 –

4

我会强烈建议使用工厂像一个模式。

如果这是可能的:

。将所有孩子和抽象类别推入单独的程序集

。像内部方法一样声明孩子的名字,所以没有人可以通过调用ctor来构造它们。

。实现Factory类来构造调用者指定的对象类型,在实际创建一个对象之后,但在将它返回给调用者之前,这种类型将会抽象调用抽象的DoStuff()方法。

好东西关于这个:它会给你更多的抽象层次,所以如果将来你需要一些更多的函数调用或任何其他类型的逻辑复杂度,你将需要的只是将它们添加到您的Factory类中。

也就是说。

问候

+0

独立程序集不可能的想法:对于DEBUG编译,在基本ctor中有一个方法来查看堆栈并确保它是由工厂方法调用的。 –

+0

另一个替代想法:将子类移动到它自己的名称空间,可能带有一个名称,例如MyNamespace.DoNotUseThisUseFactoryMethodInstead,所以您不使用...;它并且意外地使用小孩。 –

0

在WPF应用程序,你可以用Dispatcher帮助推迟DoStuff()的invokation:

abstract class Parent 
{ 
    public Parent() 
    { 
     Dispatcher.CurrentDispatcher.BeginInvoke(new Action(this.DoStuff)); 
    } 

    private void DoStuff() 
    { 
     // stuff, could also be abstract or virtual 
    } 
} 

但是,它不能保证DoStuff()将构造后立即调用。

2

正如其他人所说,你应该使用工厂模式。

public class Parent 
{ 
    public Parent() 
    {    
    } 

    public virtual void PostConstructor() 
    { 
    } 
} 

public class Child : Parent 
{ 
    public override void PostConstructor() 
    { 
     base.PostConstructor(); 

     // Your code here 
    } 
} 

public void FactoryMethod<T>() where T : Parent 
{ 
    T newobject = new T(); 
    newobject.PostConstructor(); 
} 
0

让我来介绍一个使用一些C#特性的通用解决方案。请注意,此解决方案不需要在构造对象后使用工厂模式或调用任何模式,并且它仅适用于任何类,只需使用单一方法实现接口即可。 首先声明我们的类将有一个接口来实现:

public interface IInitialize { 
    void OnInitialize(); 
} 

接下来我们添加静态扩展类此接口,并添加初始化方法:

public static class InitializeExtensions 
{ 
    public static void Initialize<T>(this T obj) where T: IInitialize 
    { 
     if (obj.GetType() == typeof(T))  
      obj.OnInitialize(); 
    } 
} 

现在,如果我们需要一个类及其所有后代在对象完全构建后立即调用初始化方法,我们需要做的就是执行IInitialize并在构造函数中追加一行:

public class Parent : IInitialize 
{ 
    public virtual void OnInitialize() 
    { 
     Console.WriteLine("Parent"); 
    } 

    public Parent() 
    { 
     this.Initialize(); 
    } 
} 

public class Child : Parent 
{ 
    public Child() 
    { 
     this.Initialize(); 
    } 

    public override void OnInitialize() 
    { 
     Console.WriteLine("Child"); 
    } 
} 

public class GrandChild : Child 
{ 
    public GrandChild() 
    { 
     this.Initialize(); 
    } 

    public override void OnInitialize() 
    { 
     Console.WriteLine("GrandChild"); 
    } 
} 

诀窍是当一个派生类调用扩展方法Initialize时,这将抑制任何不是来自实际类的调用。