>> It's true that a >> reference to the new instance is not returned, but the instance is >> always 'created' (I'm not sure what the definition of 'construction' is >> in this case, but certainly an object id is allocated, etc.). This is >> obviously the case, since you have access to the 'this' pointer inside >> the constructor. > >Right - and I do not fully remember object initialization details, but when >a constructor starts execution, the object has already its fields >initialized (to default values or some other values), so it exists. >Moreover, I believe that there is another difference from C++ - a base >class' constructor may call virtual methods (that refer to >not-yet-constructed derived class' object) - this is not elegant and this >is >an error in many cases, but is possible. In C++ (unmanaged), if my memory >serves well, this is undefined and generally is a serious application >error. >Maybe due to different vtable initialization rules (mentioned in one of >earlier posts). E.g., in C++, it is possible to call "pure virtual >function" >in such a way.
Thanks for making that point. Overrides on GetHashcode() and Equals() for immutable objects seem like examples of where this might be a real problem. A base class might cause GetHashcode() to be called before the fields that determine its value have been set. >> I hacked something together to check this out. As a result I've come up >> with a new coding rule for myself: 'if the constructor is going to >> throw, then the constructor has to guarantee that it hasn't configured >> another instance with a reference to itself'. > >I would change this rule to the following: Do not pass a reference to >itself >anywhere until the object is fully functional (configured). Even if the >constructor is "not going to throw" (theoretically, every line of code can >throw something ;-) ), what will happen if other threads start using the >object in an uninitialized state? What means that: >- if a constructor has to pass a reference to itself, it will do it in "the >last line of code" >- care should be taken for non-sealed classes (!) You make a valid point, but if anything I think you've really made my point for not being able to 'handle' exceptions. Seriously, the code is imperative, you have to assume it works, if it doesn't work then you can rarely properly recover. Making each function entirely atomic is a serious chore that is just not realistic, but this is where your reasoning will end up. Instead, I recommend that you don't pretend that you can handle exceptions and make sure you avoid them (when you can). >> If you run this code, you'll see three partially constructed classes >> handle the publisher's event. This is definitely something to watch out >> for. > >I would say that they are completely constructed but not completely >initialized (but will not fight for words). That's an excellent point. I reckon the term 'initializer' is far more suitable than 'constructor'. Unless you want to call what the runtime does 'creation' instead of 'construction' and then call 'initialization' construction. The point is they are two discrete processes. >> At the end of the day, I tend to think of exceptions as being too >> difficult to handle. I design my code such that exceptions don't happen. > >I think that you are going definitely too far. Exceptions are a great way >of >error reporting and handling, but as everything, there are always some >problems involved. Are you still using the "old-good" C-style with a >returned status? In a programming language with automatic GC? Interesting. You assumed my answer to your own question was yes. There is no way that I'd do this. I've been taught to code such that a 'function returns something and changes nothing' and a 'routine changes something and returns nothing'. A method will always work if the post conditions are met, if it doesn't then your application has an error and can not continue processing. >> If they do, then I have a bug and I need to fix it, I don't pretend that >> I can really 'handle' an exception (obviously there are cases where you >> have to, but I try to avoid these), despite its politically correct name >> I still consider it an 'error' and I generally terminate my process. To >> try and handle all the possible states that you could be in when you >> receive an exception seems almost impossible to me. > >How about ThreadInterruptedException? How about temporary communication >errors? Etc. There are lots of errors that could be gracefully handled by >an application. Yep. That's why I used words like 'tend', 'prefer' and 'try'. I know it's not a perfect world. My point is that if you get an exception it's very difficult to make assertions about your state. >A small remark: Note that if the first object would succeed and the >remaining two failed, then the first one would be Detach()ed three times. Eeek! How embarrassing. You're right of course. *hides*. Below is an example of an application that tries to handle an exception from a simple property assignment. What would you have your application do in this situation? I'd terminate the process. John. public class EntryPoint { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] public static void Main(string[] args) { try { SomeApplication app = new SomeApplication(); app.Run(); } catch (Exception ex) { // log your error. Console.WriteLine(ex); // tell your user something Console.WriteLine("Error while processing. Processing aborted. " + "Error logged for diagnosis."); // and terminate the process. // next time, don't write buggy code. } Console.Write("Press ENTER to exit."); Console.ReadLine(); } } public class SomeApplication { private DataProvider _provider; Consumer[] _consumers; public SomeApplication() { this._provider = new DataProvider(); this._consumers = new Consumer[] { new DataConsumer(this._provider), new BuggyConsumer(this._provider), new AnotherConsumer(this._provider), new DataConsumer(this._provider), new BuggyConsumer(this._provider), new AnotherConsumer(this._provider) }; } public void Run() { for (Int32 i = 0; i < 10; i++) { try { this._provider.SomeField = i.ToString(); } catch (ApplicationException ex) { // OK, you've caught an exception. // Well done. // Your process can stay alive. // Want to hazard a guess as to the state of this application's data? // not me.. throw new ApplicationException("I have no idea what's going on.", ex); } } } } public class DataProvider { private String _someField; public event EventHandler SomeFieldChanged; public String SomeField { get { return this._someField; } set { if (this._someField == value) return; this._someField = value; this.OnSomeFieldChanged(EventArgs.Empty); } } private void OnSomeFieldChanged(EventArgs e) { if (this.SomeFieldChanged != null) this.SomeFieldChanged(this, e); } } public abstract class Consumer { private Int32 _someCounter; private String _someFieldCache; public String SomeFieldCache { get { return this._someFieldCache; } } public Int32 SomeCounter { get { return this._someCounter; } } public void SetSomeField(String value) { this._someFieldCache = value; this._someCounter++; } } public class DataConsumer : Consumer { public DataConsumer(DataProvider provider) { provider.SomeFieldChanged += new EventHandler(this.HandleSomeFieldChanged); } private void HandleSomeFieldChanged(Object sender, EventArgs e) { Console.WriteLine("DataConsumer[{0}]::HandleSomeFieldChanged(..)", this.GetHashCode()); this.SetSomeField(((DataProvider)sender).SomeField); } } public class BuggyConsumer : Consumer { public BuggyConsumer(DataProvider provider) { provider.SomeFieldChanged += new EventHandler(this.HandleSomeFieldChanged); } private void HandleSomeFieldChanged(Object sender, EventArgs e) { Console.WriteLine("BuggyConsumer[{0}]::HandleSomeFieldChanged(..)", this.GetHashCode()); this.SetSomeField(((DataProvider)sender).SomeField); // emulate some logic condition that causes an exception if (this.SomeCounter > 3) { throw new ApplicationException(); } } } public class AnotherConsumer : Consumer { public AnotherConsumer(DataProvider provider) { provider.SomeFieldChanged += new EventHandler(this.HandleSomeFieldChanged); } private void HandleSomeFieldChanged(Object sender, EventArgs e) { Console.WriteLine("AnotherConsumer[{0}]::HandleSomeFieldChanged(..)", this.GetHashCode()); this.SetSomeField(((DataProvider)sender).SomeField); } } =================================== This list is hosted by DevelopMentorŪ http://www.develop.com Some .NET courses you may be interested in: NEW! Guerrilla ASP.NET, 17 May 2004, in Los Angeles http://www.develop.com/courses/gaspdotnetls View archives and manage your subscription(s) at http://discuss.develop.com