CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Patrick Smacchia [MVP C#]

October 2007 - Posts

  • Optimal Encapsulation

     

    There is a very simple thing to do to rationalize a code base: making sure that every methods, fields and types have an optimal visibility. For example, if a method is declared internal but is not used outside its declaring type, it should be declared private. For such method the private visibility is its optimal one. Rationalizing a code base with optimal visibility help protecting from potential flaw. For example having optimal visibility can avoid improper coupling with a class that should have been declared internal as soon as it was developed.

     

    Since the early days of NDepend, the tool was able to tell which code elements do not have optimal visibility. We now moved this feature into Code Query Language v1.5 with the following CQL conditions:

     

    CouldBeInternal, CoulBeInternalProtected,  CouldBeProtected, CouldBePrivate and ShouldBePublic.

     

    The default set of CQL constraints comes with a new Optimal Encapsulation group that contains constraints such as…

     

    SELECT METHODS WHERE CouldBePrivate

     

    …and that you can adapt to suit the needs of your own code base…

     

    SELECT METHODS OUT OF NAMESPACES "MyNamespace1", "MyNamespace2" WHERE CouldBePrivate AND !NameLike "MyMethodsThatIDontWantToBePrivate"…

     

    Of course, if your code base is a framework, there are chances that the query…

     

    SELECT METHODS WHERE CouldBeInternal

     

    …returns a lot of public methods that are not used by your code base, but that should remain public anyway because some external client code relies on it. In this case, just discard this query.

     

     

    What about the condition ShouldBePublic? This condition matches types, methods and fields that are declared as internal but that are used by code declared in other assemblies thanks to the attribute System.Compilers.Services.InternalsVisibleToAttribute. In general, having code elements that are considered as ShouldBePublic is not a problem because it is made on purpose. However, it can be interesting to list them.

    The condition CoulBeInternal can also apply to namespaces although .NET languages (C#, VB.NET etc) don’t allow visibility modifier on namespaces. A namespace is considered as CoulBeInternal if it contains only types that are used internally and if at least one of its types is declared as public.

     

    SELECT NAMESPACES WHERE CouldBeInternal

     

    While developing the optimal visibility feature, we notice an interesting thing. When you declare an internal class without  an explicit constructor, the default constructor automatically provided by the C# compiler is public. NDepend will tell you to declare explicitly an internal constructor but beware here: having a default internal constructor can sometime lead to serialization issues.

     

  • Refactoring estimation

     

    As every team that cares for quality and agility, we (the NDepend team) are constantly refactoring the code we modify. Since NDepend became a professional tool (feb 2007), it is mandatory to cover at least 98% of the code refactored with automatic tests. Of course 100% is prefereable but it is sometime not reachable because of some specific features such as code that popups some MessageBox or code that is specific to .NET 1.0.

     

    Since the beginning, we are measuring our refactoring productivity and my personnal number is 180: in average I’m able to refactor and write tests for 180 lines of C# code a day. With experience, I’m learning to distinguish between hard and easy refactoring. My guess mainly depends on following factors:

    • existing test coverage,
    • existing object model,
    • coupling between the code to refactor and the rest of the code.
    • number of feature to add during the refactoring.
    • number of bugs corrected in the code to refactor,

     

    For hard refactoring, I estimate that I will refactor just 100 lines of code a day. For easy one, it can goes up to 400. The important point is to keep a trace of the estimation and of the time actually spent to get better estimations in the future.

     

    As a result, I now know precisely how long it will take to refactor a portion of our code base before actually doing it. My opinion is that it relieves a lot of stress.

     

     

  • A free library to handle common and complex path operations

    I just release the library NDepend.Helpers.FilePathDirectory on CodePlex. 

    NDepend.Helpers.FilePathDirectory is the library used by the tool NDepend to handle common path operations. Benefits of the NDepend.Helpers.FilePathDirectory over the .NET Framework class System.IO.Path include:

    • Strongly typed File/Directory path.
    • Relative / absolute path conversion.
    • Path normalization API
    • Path validity check API
    • Path comparison API
    • Path browsing API.
    • Path rebasing API
    • List of path operations (TryGetCommonRootDirectory, GetListOfUniqueDirsAndUniqueFileNames, list equality…)


    NDepend.Helpers.FilePathDirectory is 100% unit-tested and tests code is provided with the source code.

    Here is a simple class diagram of the object model:

     

     

    Here is some code that shows common NDepend.Helpers.FilePathDirectory use cases:
     

    using NDepend.Helpers.FileDirectoryPath;

    using System.Diagnostics;

    using System.Collections.Generic;

     

    class Program {

       static void Main(string[] args) {

          FilePathAbsolute filePathAbsolute1, filePathAbsolute2;

          FilePathRelative filePathRelative1;

          DirectoryPathAbsolute directoryPathAbsolute1;

          DirectoryPathRelative directoryPathRelative1;

     

     

          //

          //  Path normalization

          //

          filePathAbsolute1 = new FilePathAbsolute(@"C:/Dir1\\File.txt");

          Debug.Assert(filePathAbsolute1.Path == @"C:\Dir1\File.txt");

     

          directoryPathAbsolute1 = new DirectoryPathAbsolute(@"C:/Dir1\\Dir2\");

          Debug.Assert(directoryPathAbsolute1.Path == @"C:\Dir1\Dir2");

     

          directoryPathAbsolute1 = new DirectoryPathAbsolute(@"C:\Dir1\..\Dir2\.");

          Debug.Assert(directoryPathAbsolute1.Path == @"C:\Dir2");

     

     

          //

          // Path comparison

          //

          filePathAbsolute1 = new FilePathAbsolute(@"C:/Dir1\\File.txt");

          filePathAbsolute2 = new FilePathAbsolute(@"C:\DIR1\FILE.TXT");

          Debug.Assert(filePathAbsolute1.Equals(filePathAbsolute2));

          Debug.Assert(filePathAbsolute1 == filePathAbsolute2);

     

     

          //

          // Relative -> Absolute path conversion

          //

          filePathRelative1 = new FilePathRelative(@"..\..\Dir1\File.txt");

          directoryPathAbsolute1 = new DirectoryPathAbsolute(@"C:\Dir2\Dir3\Dir4");

          filePathAbsolute1 = filePathRelative1.GetAbsolutePathFrom(directoryPathAbsolute1);

          Debug.Assert( filePathAbsolute1.Path == @"C:\Dir2\Dir1\File.txt");

     

     

          //

          // Absolute -> Relative path conversion

          //

          filePathAbsolute1 = new FilePathAbsolute(@"C:\Dir1\File.txt");

          directoryPathAbsolute1 = new DirectoryPathAbsolute(@"C:\Dir2\Dir3\Dir4");

          filePathRelative1 = filePathAbsolute1.GetPathRelativeFrom(directoryPathAbsolute1);

          Debug.Assert(filePathRelative1.Path == @"..\..\..\Dir1\File.txt");

     

     

          //

          // Path string validation

          //

          string reason;

          Debug.Assert(PathHelper.IsValidAbsolutePath(@"C:\Dir2\Dir1", out reason));

          Debug.Assert(!PathHelper.IsValidAbsolutePath(@"C:\..\Dir1", out reason));

          Debug.Assert(!PathHelper.IsValidAbsolutePath(@".\Dir1", out reason));

          Debug.Assert(!PathHelper.IsValidAbsolutePath(@"1:\Dir1", out reason));

          Debug.Assert(PathHelper.IsValidRelativePath(@".\Dir1\Dir2", out reason));

          Debug.Assert(PathHelper.IsValidRelativePath(@"..\Dir1\Dir2", out reason));

          Debug.Assert(PathHelper.IsValidRelativePath(@".\Dir1\..\Dir2", out reason));

          Debug.Assert(!PathHelper.IsValidRelativePath(@".\Dir1\..\..\Dir2", out reason));

          Debug.Assert(!PathHelper.IsValidRelativePath(@"C:\Dir1\Dir2", out reason));

     

     

          //

          // File name & extension

          //

          filePathAbsolute1 = new FilePathAbsolute(@"C:\Dir1\File.cs.Txt");

          Debug.Assert(filePathAbsolute1.FileName == "File.cs.Txt");

          Debug.Assert(filePathAbsolute1.FileNameWithoutExtension == "File.cs");

          Debug.Assert(filePathAbsolute1.FileExtension == ".Txt");

          Debug.Assert(filePathAbsolute1.HasExtension(".txt"));

     

     

          //

          // Path browsing

          //

          filePathAbsolute1 = new FilePathAbsolute(@"C:\Dir1\File.cs.Txt");

          Debug.Assert(filePathAbsolute1.ParentDirectoryPath.Path == @"C:\Dir1");

          Debug.Assert(filePathAbsolute1.GetBrotherFileWithName("File.xml").Path == @"C:\Dir1\File.xml");

          Debug.Assert(filePathAbsolute1.ParentDirectoryPath.GetChildDirectoryWithName("Dir2").Path == @"C:\Dir1\Dir2");

          Debug.Assert(filePathAbsolute1.ParentDirectoryPath.GetChildDirectoryWithName("..").Path == @"C:");

     

          directoryPathRelative1 = new DirectoryPathRelative(@"..\Dir1\Dir2");

          Debug.Assert(directoryPathRelative1.ParentDirectoryPath.Path == @"..\Dir1");

     

     

          //

          // Path rebasing

          //

          directoryPathAbsolute1 = new DirectoryPathAbsolute(@"C:\Dir1\Dir2\Dir3");

          DirectoryPathAbsolute directoryPathAbsolute2 = new DirectoryPathAbsolute(@"E:\Dir4\Dir1");

          DirectoryPathAbsolute rebasedPath;

          PathHelper.TryRebasePath(directoryPathAbsolute1, directoryPathAbsolute2, out rebasedPath);

          Debug.Assert(rebasedPath.Path == @"E:\Dir4\Dir1\Dir2\Dir3");

     

     

          //

          // List of path  ListOfPathsEquals \ Contains \ TryGetCommonRootDirectory

          //

          List<DirectoryPathAbsolute> list1 = new List<DirectoryPathAbsolute>();

          List<DirectoryPathAbsolute> list2 = new List<DirectoryPathAbsolute>();

          list1.Add(new DirectoryPathAbsolute(@"C:\Dir1\Dir2"));

          list2.Add(new DirectoryPathAbsolute(@"c:\dir1\dir2"));

          list1.Add(new DirectoryPathAbsolute(@"C:\Dir1\Dir3\Dir4"));

          list2.Add(new DirectoryPathAbsolute(@"c:\dir1\dir3\dir4"));

          Debug.Assert(ListOfPathHelper.ListOfPathsEquals(list1, list2));

          Debug.Assert(ListOfPathHelper.Contains(list1, new DirectoryPathAbsolute(@"C:\Dir1\dir2")));

          ListOfPathHelper.TryGetCommonRootDirectory(list1, out directoryPathAbsolute1);

          Debug.Assert(directoryPathAbsolute1.Path == @"C:\Dir1");

     

          //

          // List of path   GetListOfUniqueDirsAndUniqueFileNames

          //

          List<FilePathAbsolute> list = new List<FilePathAbsolute>();

          list.Add(new FilePathAbsolute(@"E:\Dir1\Dir2\File1.txt"));

          list.Add(new FilePathAbsolute(@"E:\dir1\dir2\File2.txt"));

          list.Add(new FilePathAbsolute(@"E:\Dir1\Dir2\Dir3\file2.txt"));

          List<DirectoryPathAbsolute> listOfUniqueDirs;

          List<string> listOfUniqueFileNames;

          ListOfPathHelper.GetListOfUniqueDirsAndUniqueFileNames(list, out listOfUniqueDirs, out listOfUniqueFileNames);

          Debug.Assert(listOfUniqueDirs.Count == 2);

          Debug.Assert(listOfUniqueDirs[0].Path == @"E:\Dir1\Dir2");

          Debug.Assert(listOfUniqueDirs[1].Path == @"E:\Dir1\Dir2\Dir3");

          Debug.Assert(listOfUniqueFileNames.Count == 2);

          Debug.Assert(listOfUniqueFileNames[0] == "File1.txt");

          Debug.Assert(listOfUniqueFileNames[1] == "File2.txt");

     

     

          //

          // Interaction with System.IO API

          //

          filePathAbsolute1 = new FilePathAbsolute(

             System.Reflection.Assembly.GetExecutingAssembly().Location);

          Debug.Assert(filePathAbsolute1.Exists);

          System.IO.FileInfo fileInfo = filePathAbsolute1.FileInfo;

     

          directoryPathAbsolute1 = filePathAbsolute1.ParentDirectoryPath as DirectoryPathAbsolute;

          Debug.Assert(directoryPathAbsolute1.Exists);

          System.IO.DirectoryInfo directoryInfo = directoryPathAbsolute1.DirectoryInfo;

     

          List<DirectoryPathAbsolute> listSubDir = directoryPathAbsolute1.ChildrenDirectoriesPath;

          List<FilePathAbsolute> listSubFile = directoryPathAbsolute1.ChildrenFilesPath;

     

       }

    }

  • Simplify your unit test code with some C# anonymous methods

    While writing some unit test to check that an event was indeed triggered, I figured out that anonymous methods can be a great help to avoid writing some mock handler methods.

    Here is the case. I have a class Foo that implements the System.ComponentModel.INotifyPropertyChanged interface. This interface has just one event, PropertyChanged, that will let its handlers know about the name of the property of Foo that have changed. Basically, my unit test looks like:

     

    [Test]

    public void Test_ModifyReport_NotifyPropertyChanged() {

       int nbProp1Changed = 0;

       int nbProp2Changed = 0;

       // ...

     

       Foo foo = new Foo();

     

       foo.PropertyChanged +=

          delegate(object sender,

                   PropertyChangedEventArgs propertyChangedEventArgs) {

          Debug.Assert(sender == project);

          switch (propertyChangedEventArgs.PropertyName) {

             case Constants.PROPERTY1_NAME:

                nbProp1Changed ++; return;

             case Constants.PROPERTY2_NAME:

                nbProp2Changed ++; return;

             // ...

          }

       };

     

       Assert.IsTrue(Foo.Prop1 != 3);

       Assert.IsTrue(nbProp1Changed == 0);

       Foo.Prop1 = 3;

       Assert.IsTrue(nbProp1Changed == 1);

     

       // Test PropertyChanged not triggered when the value is the same

       Foo.Prop1 = 3;

       Assert.IsTrue(nbProp1Changed == 1);

       //…

    }

    Of course, if you have a large number of properties, you could create a dictionary of ‘Number of times the property X has been changed’ indexed by ‘The property X name’.

    The beauty of this trick is that it avoids creating some handlers method and corresponding field state to check that an event is triggered. Actually, this code is much more tricky than it looks like at first glance. Under the hood, the C# compiler is creating a dedicated class to hold the nbXXXChanged value (see with Reflector).  We say that the states nbXXXChanged have been captured by the anonymous method. We are then talking with a functional programming vocabulary because in fact we created what is named a closure in the functional world.

    I won’t go into the details of anonymous method compilation and closure because I described all this in an article published on TSS.NET 3 years ago (read it here). You can also read the second part of the article about the yield keyword here.

  • Unit Test vs. Debug.Assert()

     

    How should your automatic tests behave when they are executing an assertion through  System.Diagnostics.Debug.Assert(…)?

     

    I have been mulling over this question because by default, the tool TestDriven.NET ignored my assertions. Concretely, while executing tests with TD.NET, the Assertion Failed! window doesn’t appear for violated assertions. You are certainly happy that the Assertion Failed! windows appear when you’re doing your manual tests, then why would you disable this behavior in your automatic tests?

     

    I asked my friend Jamie Cansdale (the guy behind TD.NET) how to remedy this behavior and it is as simple as executing the 2 following lines in your test appdomain before executing any tests:

     

    System.Diagnostics.DefaultTraceListener listener =     

       (System.Diagnostics.DefaultTraceListener)

       System.Diagnostics.Trace.Listeners[0];

    listener.AssertUiEnabled = true;

     

    Let me now explain why I wished this behavior.

    • Why do we use assertions in our code? Because C# and VB.NET don’t provide yet facilities to write contract.
    • What is a contract then? Simply put, a contract is a condition that should never be violated. If a contract is violated, it means that there is a bug somewhere.
    • Then, should automatic tests be allowed to break contract? According to me, the answer is no. Automatic tests are here to detect bugs, they should not simulate bugs.

    Let’s analyze a concrete example:

     

    public class Foo {

       public static void PublicMethod(string s) {

          if (s == null) { throw new ArgumentNullException();}

          // ...

       }

       internal static void InternalMethod(string s) {

          Debug.Assert(s != null);

          // ...

       }

    }

     

    Automatic tests should test that PublicMethod() raises an exception when its input argument s is null. PublicMethod() can be called by tier code that me and my team don’t know about. It is a common defensive code pattern that protects our Foo framework from misuses.

     

    However, automatic tests should not test InternalMethod() with a null s argument because it is an internal method. As the developer of the InternalMethod() I put this assertion because in my business logic there is no sense to call InternalMethod() with a null argument. In other words, if at a point InternalMethod() is called with a null argument, it means that there is a bug somewhere in my company code (because only code from my company is allowed to call non-public code). If an automatic test is able to trigger a call to InternalMethod() with a null argument, it means that there is a bug somewhere in my company code and I certainly want to know about it.

     

    Of course, enabling assertion during automatic tests can lead to broken build process. My opinion is that this is a risk that is worth being taken because an assertion that fails necessarily means that there is somewhere a bug, a flawed contract or a flawed automatic test.

     

    Notice that in our particular Foo example, if non-nullable types were supported in C# (as I strongly advocated for here) there wouldn’t be any question and any null argument bug would be discovered at compile-time.

     


     

  • How to isolate your integration tests from your unit tests ?

    Recently, we needed to isolate our integration tests from our unit tests. They were mixed in the same test project and we wanted to avoid the cost of executing integration tests while executing our unit tests. Integration tests takes longer to be executed because they are identified as tests that are accessing external systems, such as DB, files, registry, web service…

     

    At first, it looked like a daunting task because we needed to know which tests were triggering file access and we have thousands of tests. More precisely, we needed to know which test method from our test assembly were depending on the class System.IO.File or depending on the method System.Xml.XmlDocument.Load(String). Hopefully, we realized that a simple CQL query would list all integration tests.

     

    SELECT METHODS FROM ASSEMBLIES "NDepend.Test.Unit" WHERE

    (IsUsing "System.IO.File" OR IsUsing "System.Xml.XmlDocument.Load(String)")

     

    This CQL query works well because the condition IsUsing XXX matches direct and indirect use. It means that if Test1() is calling a method that is calling a method that … that is calling XmlDocument.Load(String), then Test1() will be identified as an integration test. Notice that if an interface is used to mock a service (such as file or DB access), the IsUsing condition won’t see what’s hidden behind the interface. Also, if our application was using DB, we would have needed some more DB conditions such as

     

    WHERE IsUsing "System.Data.Common.DBCommand" OR IsUsing "System.Data.Common.IDBConnection" OR

     

    We also used the Dependencies Matrix to see which test was using which file method. A blue cell tagged with X means that the test method (in abscissa) is using the file method (in ordinate) with a minimum path depth of X. For example, we can see that the test Test_CheckNDependDeployment_Normal() is using Files.Exist() with a depth of 4 (see the generated graph below the matrix):

     


     

    Because NDepend relies on static analysis (i.e it is analyzing the code structure, not the run-time call graph), we got a few false positives. Indeed, some test methods were statically depending on a file access method without accessing it at run-time. However, we decided to consider such tests as integration tests, this way we can keep our simple criteria to sort between unit and integration tests.

  • Why is it useful to count the number of Lines Of Code (LOC) ?

    My previous post explained How to count the LOC of your .NET application and Tim B and Keith answered that LOC should never be counted because it is not useful. I don’t agree at all.

     

    Often developers get stressed when we talk about LOC because some company unfortunatly use LOC as a yardstick to assess developers productivity. LOC has nothing to do with productivity: you can create 1000 LOC in a day by creating a Form with many controls and it can also take 2 days to correct a hard bug that will be fix by changing a single line.

     

    First, LOC is useful to compute the automatic test coverage ratio, since coverage necessarily represents a fraction of LOC.

     

    Second, LOC and especially logical LOC is useful to estimate software. Estimation in software is a difficult task. In his excellent book Software Estimation Demysitfying the Black Art, Steve Mc Connell explains well that LOC is the most efficient way to compare applications that have been developed within the same context. You can then use this comparison to plan new development, refactoring, migration etc… For example it is interesting to know that the code base of Windows Vista (around 70M LOC) is roughly 100 times bigger than the .NET framework codebase (around 500K LOC).

     

    Estimating software is a complex subject because the estimation is not proportional to LOC, the bigger a codebase is, the higher it costs to add a new feature.



  • How do you count your number of Lines Of Code (LOC) ?

    Do you count method signature declaration? Do you count lines with only bracket? Do you count several lines when a single method call is written on several lines because of a high number of parameters? Do you count ‘namespaces’ and ‘using namespace’ declaration? Do you count interface and abstract methods declaration? Do you count fields assignment when they are declared? Do you count blank line?

    Depending on the coding style of each of developer and depending on the language choose (C#, VB.NET…) there can be significant difference by measuring the LOC.

    Apparently measuring the LOC from parsing source files looks like a complex subject. Thanks to an astute there exists a simple way to measure exactly what is called the logical LOC. The logical LOC has 2 significant advantages over the physical LOC (the LOC that is inferred from parsing source files):

    • Coding style doesn’t interfere with logical LOC. For example the LOC won’t change because a method call is spawn on several lines because of a high number of arguments.
    • Logical LOC is independent from the language. Values obtained from assemblies written with different languages are comparable and can be summed.

    In the .NET world, the logical LOC can be computed from the PDB files, the files that are used by the debugger to link the IL code with the source code. The tool NDepend computes the logical LOC for a method this way: it is equals to the number of sequence point found for a method in the PDB file. A sequence point is used to mark a spot in the IL code that corresponds to a specific location in the original source. More info about sequence points here. Notice that sequence points which correspond to C# braces‘{‘ and ‘}’ are not taken account.

    Obviously, the LOC for a type is the sum of its methods’ LOC, the LOC for a namespace is the sum of its types’ LOC, the LOC for an assembly is the sum of its namespaces’ LOC and the LOC for an application is the sum of its assemblies LOC. Here are some observations:

    • Interfaces, abstract methods and enumerations have a LOC equals to 0. Only concrete code that is effectively executed is considered when computing LOC.
    • Namespaces, types, fields and methods declarations are not considered as line of code because they don’t have corresponding sequence points.
    • When the C# or VB.NET compiler faces an inline instance fields initialization, it generates a sequence point for each of the instance constructor (the same remark applies for inline static fields initialization and static constructor).
    • LOC computed from an anonymous method doesn’t interfere with the LOC of its outer declaring methods.
    • The overall ratio between NbILInstructions and LOC (in C# and VB.NET) is usually around 7.
  • Encapsulate your Fields at object level

    There is one interesting detail of modern OOP language (C#, Java, VB.NET, C++…) that often developers are not aware of: the ‘private encapsulation’ applies at class level and not at object level. For example, in the following program, we can see that the object foo1 is able to change the private state of the object foo2.

    class Foo {

       private int m_Field = 0;

       internal void UpdateField(Foo foo) {

          foo.m_Field = 1;

       }

       static void Main() {

          Foo foo1 = new Foo();

          Foo foo2 = new Foo();

          foo1.UpdateField(foo2);

       }

    }

    To avoid potential corrupted state, we often want to encapsulate our fields at object level. Thanks to the IsDirectlyWritingField CQL condition, you can restrict the writing access to m_Field to a single property setter. By using the NDepend.CQL.CQLConstraint attribute (found in $NDepend Install Dir$\Lib\NDepend.CQL.dll), you can tag the declaration of m_Field directly with such CQL constraint. Then, our example can be rewritten like this:

    using NDepend.CQL;

     

    class Foo {

       [CQLConstraint(@"// <Name>Encapsulate m_Field state at object level</Name>

    WARN IF Count > 0 IN SELECT METHODS WHERE

    IsDirectlyWritingField ""Foo.m_Field""

    AND !FullNameIs ""Foo.set_Field(Int32)"" ")]

       private int m_Field = 0;

       private int Field { set { m_Field = value; } }

       internal void UpdateField(Foo foo) {

          foo.Field = 1;

          // 'foo.m_Field = 1;' would provoke a CQL constraint warning.

       }

       static void Main() {

          Foo foo1 = new Foo();

          Foo foo2 = new Foo();

          foo1.UpdateField(foo2);

       }

    }

    This way, we will be automatically warned by the CQL constraint as soon as another method than the Field property setter assigns directly m_Field.

     

    Update: Jason asked below why this is useful to encapsulate the access to a field. I admit that I find this possibility so useful that I didn't take the time to explain the 'why' properly.

    A very common need is to be able to track at debugging time all writing access to a field. This way you can figure out why and when it is assigned to the invalid state you noticed. By encapsulating your field this way, you just have to put a single breakpoint to achieve this (instead of having to put N breakpoints at th N places where your field get assigned).  Unfortunatly, the debugger don't provide such facility yet (I remember that I have read somewhere that it is a feature hard to provide because of all underlying CLR optimizations).

    It can also be useful to intercept all assignement to your field to do something special, such as logging, transforming the value, counting the number of writing etc...


     

     

    A very common need for object encapsulation (not handled by the VisualStudio debugger), is to be able to track at debugging time all writing access to a field because you figured out that it ends up with an invalid state.

More Posts

This Blog

Syndication