简体   繁体   English

一般的OOP和特别的C# - 构造函数应该快速返回吗?

[英]OOP in General and C# in Particular - Should the constructor return quickly?

I'm currently refactoring some old code for my work. 我正在为我的工作重构一些旧代码。 Some idiot (me, 2 years ago) wrote a few things that I think stink. 一些白痴(我,2年前)写了一些我觉得很臭的东西。 I have this feeling in my gut (I've might read somewhere and forgotten the source) that a constructor in C# should return quickly, because of some technical detail, possibly to do with garbage collection. 我有这种感觉(我可能会在某处阅读并忘记源代码)C#中的构造函数应该快速返回,因为有一些技术细节,可能与垃圾收集有关。 Ie the following 即以下

class A
{
     public object Result {get; private set;}
     private object RunLongOperation(){/* ... */}
     public A(){
         Result = RunLongOperation();
     }
}

is bad practise. 是不好的做法。 So my question is twofold - Is it actually bad, and if so why? 所以我的问题是双重的 - 它真的很糟糕,如果是这样,为什么呢? The above can be rewritten as 以上可以改写为

class A
{
     public object Result {get; private set;}
     private static object RunLongOperation(){/* ... */}
     private A() { }
     public static A Make(){
        return  new A { Result = RunLongOperation() };
     }
}

through a kind of factory static method. 通过一种工厂静态方法。 This to me just seems more code than necessary, but the actual object is constructed quickly. 这对我来说似乎代码不是必要的,但实际的对象很快就构建了。

To shine a light, the constructor takes a few parameters and renders an image in RunLonOperation() , and does some other stuff based on the input parameters. 为了发光,构造函数接受一些参数并在RunLonOperation()呈现图像,并根据输入参数执行其他一些操作。 The class then reduces to immutable result container. 然后该类缩减为不可变结果容器。 The operation takes about 10 to 20 seconds, based on parameters. 根据参数,操作大约需要10到20秒。

Yes doing real work in a constructor is a bad thing from a testability point of view. 从可测试性的角度来看,在构造函数中进行实际工作是件坏事。

It is very hard to write a test for a class that does heavy work in the constructor because you don't have any means left to change the dependencies needed for that object or to inject some custom behaviour by mocking the object. 为在构造函数中执行繁重工作的类编写测试非常困难,因为您没有任何方法可以更改该对象所需的依赖项或通过模拟对象注入一些自定义行为。

See http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/ for a good explanation of this design flaw. 有关此设计缺陷的详细解释,请参见http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

I don't think that there is a technical requirement (eg GC) that a constructor should not take more than a certain time. 我认为构造函数不应该花费超过一定时间的技术要求(例如GC)。 However from a programmers point of view, I certainly do not expect that new -ing an object will take a long time. 但是从程序员的角度来看,我当然不希望new的对象需要很长时间。 The factory method seems better suited for that use to me. 工厂方法似乎更适合我的用途。

You also might consider removing the long running operation and injecting the result into the constructor and have a RenderImageFactory instead. 您还可以考虑删除长时间运行的操作并将结果注入构造函数,并改为使用RenderImageFactory Would make the process more obvious and might help with unit testing (meaning: If you want to unit tets class A you might not want to have to render the image every time but instead be able to just mock it out to speed things up and reduce test setup overhead) 会使这个过程变得更加明显并且可能有助于单元测试(意思是:如果你想要单元化T类,你可能不希望每次都渲染图像,而是能够模拟出来以加快速度并减少测试设置开销)

我不认为应该有一个通用的规则,但实际上最好使用工厂模式,而不是在构造函数中处理太多的东西。

There is no hard rule about how long a constructor may take. 关于构造函数可能需要多长时间没有硬性规则。 It depends on what you could reasonably expect depending on what the object does. 这取决于你可以合理地期望取决于对象的作用。

If it's possible to use lazy loading, you should consider it that is good for your class. 如果可以使用延迟加载,你应该认为它对你的班级有好处。 If you for example are loading different things in the constructor, and some of them are not always used, it could be better to load them when and if they are actually needed. 例如,如果您在构造函数中加载了不同的东西,并且其中一些并不总是被使用,那么在实际需要时加载它们可能会更好。

Other than that there isn't often a good reason to put work anywhere else than the constructor, if it needs to be done anyway. 除此之外,如果需要完成工作,除了构造函数之外,通常没有充分的理由将工作放在其他地方。

There's no reason at all why a constructor should not take as long as it needs to do its job. 没有理由为什么构造函数不应该只需要完成它的工作。 The options proposed by you and others look to me like they will make your code more complex for no discernible benefit. 您和其他人提出的选项让我觉得它们会使您的代码更复杂,没有明显的好处。

Anytime we consider a long-running task call from the .ctor is a good sign of a bad design smell. 任何时候我们都会考虑来自.ctor的长时间运行的任务调用,这是一个糟糕的设计气味的好兆头。 I was always keeping this in my mind until I stuck on a similar issue, running a log code in a type .ctor. 我总是记住这一点,直到我遇到类似的问题,在类型.ctor中运行日志代码。 In my case I'm about to run a Task<T> asynchronously in the constructor and total the problem, I can't get rid of the task at all since it's all about reflections where your hands are off of any good design principle implementation. 在我的情况下,我将要在构造函数中异步运行Task<T>并完成问题,我无法完全摆脱任务,因为它完全是关于你的手没有任何好的设计原则实现的反射。

The Thread.Suspend and Thread.Pause seemed to be the only dangerous candidates for running long tasks from constructors, but they're obsolete (after 1.1) and now it seems there's no any reason to limit the execution of the .ctor but the intuitive principle of any API design (that the .ctor is simply a way to quickly "prepare" the object for further usage. MSDN has a good guideline for that). Thread.SuspendThread.Pause似乎是从构造函数中运行长任务的唯一危险候选者,但它们已经过时 (在1.1之后),现在似乎没有任何理由限制.ctor的执行但直观任何API设计的原则(.ctor只是一种快速“准备”对象以供进一步使用的方法.MSDN 有一个很好的指导方针 )。

My suggestion would be using a Factory instead, since constructors are not the place to put business logic within, typically. 我的建议是使用Factory,因为构造函数通常不是将业务逻辑放在其中的地方。 If you can refactor then you should . 如果你可以重构那么你应该 If you can't nobody will punish you for that ;) 如果你不能没有人会惩罚你;)

Be lazy: 偷懒:

class A 
{
     private A() { } 
     private static object _Result;
     public static object Result 
     {
         get
         {
              if (_Result == null)
                   _Result = RunLongOperation();
              return _Result;
         }
     }
     private static object RunLongOperation(){/* ... */} 
} 

If the long running operation is referentially transparent , then you could only run it once for each possible outcome of 'Result', and re-use those results for each object instance. 如果长时间运行的操作是引用透明的 ,那么您只能为“结果”的每个可能结果运行一次,并为每个对象实例重用这些结果。 This may provide a performance benefit. 这可以提供性能益处。 If there are multiple possible results then you'll need some way to look up which result is appropriate. 如果有多个可能的结果,那么您需要一些方法来查找哪个结果是合适的。

This is just a rough example, may need some tweaking: 这只是一个粗略的例子,可能需要一些调整:

class A 
{      
   static Dictionary<String, Object> memoizationCache;

   public static bool TryGetMemoizedResult(A instance, out object result)
   {
       // do the checking etc. 

       result = memoizationCache[instance.SomeMember]; 
   } 

   public static void AddMemoizedResult(A instance, object result)
   {
       memoizationCache.Add(instance.SomeMember, result); 
   } 

   public object Result {get; private set;}   

   public string SomeMember { get; private set; }   

   private object RunLongOperation(){/* ... */}   

   public A()
   {   
       object result;

       if (TryGetMemoizedResult(this, out result))
       {
            Result = result;
       }
       else 
       {
            Result = RunLongOperation();  
            AddMemoizedResult(this, this.Result); 
       }    
   } 
} 

The reason I bring that up is because it looks like in your case the operation may be referentially transparent, but if not, then I'd go with the lazy loading approach. 我提出这个问题的原因是因为在你的情况下看起来操作可能是引用透明的,但如果没有,那么我会采用延迟加载方法。 As it stands I dont think there should be a huge performance difference between your two initialization techniques, though. 目前我不认为你的两种初始化技术之间应该存在巨大的性能差异。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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