Welcome to MSDN Blogs Sign in | Join | Help

Speaking at Lake County .NET User’s Group

I’ll be speaking at the Lake County .NET User’s Group (LCNUG) near Chicago, Illinois on September 24th.  I’ll be talking about new features in C# 4.0, including named and optional parameters, dynamic support, scripting, office interop and No-PIA (Primary-Interop-Assemblies) support.  

The permalink for the event is here.

If you’re in the area, swing on by!

Posted by jmstall | 1 Comments

Writing a CLR Debugger in Python

Harry Pierson has written an excellent set of blog entries about writing a managed debugger in IronPython. He builds on the ICorDebug managed wrappers that we ship in Mdbg and explains many of the concepts for how to write a debugger, such as managing breakpoints. 

Read more about them here

Posted by jmstall | 0 Comments
Filed under:

ICustomQueryInterface and CLR V4

CLR V4 fixes an issue with COM-interop that’s been bothering me for a while. The problem is that unless you’re using a PIA, you can often have either your caller or callee be managed code, but not both

You can import the same COM-classic interface into managed code multiple times, and two components can naturally end up with two different .NET types representing the same single COM-classic interface. Furthermore, the types can be imported with different managed signatures because of things like [PreserveSig] attributes and different ways to marshal data types.

(PIAs are supposed to alleviate that by providing a single unified definition, but then getting multiple components to agree on that unified definition is its own problem. CLR V4 added support for to avoid requiring PIAs. see NoPia. )

When managed code calls a COM interface that is implemented by managed code (Managed –> Native –> Managed), the CLR detects that the COM-object is really a managed implementation and creates a direct managed call (Managed –> Managed). So if your caller and callee are bound to different .NET types for the interface, the CLR won’t realize it’s a COM-interface call and will just fail on the .NET type mismatch.

CLR V4 fixes this by adding the ICustomQueryInterface that lets a managed object really act as if it’s native code when being called through COM-interop.

 

Where I hit this…

I hit this as I was writing debugger code in Managed. We had a managed application (MDbg) that used COM-interop to call into a native implementation of ICorDebug. That worked great (managed calling native). Later, we had some cases of creating a managed implementation of certain ICorDebug interfaces. But that failed from Mdbg because they had different COM-interop import definitions for ICorDebug.

This also just naturally starts showing up as people are porting more and more of their legacy systems to managed code.

 

Code sample demonstrating the problem

Say you have a COM-classic interface IFoo, imported by 2 different components:

[ComImport, InterfaceType(1), ComConversionLoss, Guid("FC3E287D-D659-4E1D-81D5-9D29398C7237")]
interface IFoo1
{
    [PreserveSig]
    int Thing(int x);
}

[ComImport, InterfaceType(1), ComConversionLoss, Guid("FC3E287D-D659-4E1D-81D5-9D29398C7237")]
interface IFoo2
{        
    void Thing(int x);
}

 

Now suppose you have a class C1 that implements IFoo1. IFoo1 and IFoo2 represent the same COM interface and have the same GUID, so you’d like to be able to use them interchangeably. However, they’re 2 different .NET types. typeof(IFoo1) != typeof(IFoo2).

Ideally, you’d like the following snippet to succeed and call Thing(5) and Thing(3) on the object instance obj.

static void Main(string[] args)
{
    Console.WriteLine("Hi!");

    object obj = new C1();
    obj = GetRCW(obj); // get a COM object for C1

    IFoo1 f1 = (IFoo1)obj;
    IFoo2 f2 = (IFoo2)obj; // Fails!!! obj is really a C1, and can’t cast to a IFoo2
    f1.Thing(5);
    f2.Thing(3);
}

Run that and you get an invalid cast exception because it can’t cast C1 to a IFoo2. It doesn’t understand that IFoo2 and IFoo1 are the same interface..

C:\TEMP>b.exe
Hi!

Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'ConsoleApplication4.C1' to type 'ConsoleApplication4.IFoo2'.
   at ConsoleApplication4.Program.Main(String[] args)

 

ICustomQueryInterface to the rescue.

CLR 4 allows an object to hook ICustomQueryInterface and have fine grain control over QI calls. The CLR detects that C1 is really a managed object because of a QI for a secret interface, IManagedObject. C1 can intercept this QI call and fail it (by returning CustomQueryInterfaceResult.Failed), and thus look like a native object.  It passes all other QI calls through to the RCW’s QI handling (via returning CustomQueryInterfaceResult.NotHandled)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApplication4
{
    [ComImport, InterfaceType(1), ComConversionLoss, Guid("FC3E287D-D659-4E1D-81D5-9D29398C7237")]
    interface IFoo1
    {
        [PreserveSig]
        int Thing(int x);
    }

    [ComImport, InterfaceType(1), ComConversionLoss, Guid("FC3E287D-D659-4E1D-81D5-9D29398C7237")]
    interface IFoo2
    {        
        void Thing(int x);
    }


    class C1 : IFoo1, ICustomQueryInterface
    {

        static readonly Guid IID_IMarshal = new Guid("00000003-0000-0000-C000-000000000046");
        static readonly Guid IID_IManagedObject = new Guid("C3FCC19E-A970-11d2-8B5A-00A0C9B7C9C4");

        CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out IntPtr ppv)
        {
            if (iid == IID_IMarshal ||
                iid == IID_IManagedObject
                )
            {
                ppv = IntPtr.Zero;
                return CustomQueryInterfaceResult.Failed;
            }

            ppv = IntPtr.Zero;
            return CustomQueryInterfaceResult.NotHandled;
        }



        #region IFoo1 Members

        public int Thing(int x)
        {
            Console.WriteLine("Inside C1={0}", x);
            return 0;
        }

        #endregion
    }


   
    class Program
    {
        // Convert it to a RCW
        static object GetRCW(object o)
        {
            IntPtr ip = IntPtr.Zero;
            try
            {
                ip = Marshal.GetIUnknownForObject(o);
                return Marshal.GetObjectForIUnknown(ip);
            }
            finally
            {
                Marshal.Release(ip);
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Hi!");

            object c1 = new C1();
            object obj = GetRCW(c1);

            Console.WriteLine(c1.GetType().FullName); // ConsoleApplication4.C1
            Console.WriteLine(obj.GetType().FullName); // System.__ComObject

            IFoo1 f1 = (IFoo1)obj;
            IFoo2 f2 = (IFoo2)obj;
            f1.Thing(5);
            f2.Thing(3);
        }
    }
}

We call GetRCW() to convert the object to a Runtime Callable Wrapper (RCW) so that the CLR will actually do COM-interop dispatch. You can observe the GetType() calls on c1 vs. obj.

Notice now both calls via Thing(5) and Thing(3) succeed. And they’re even going through different import signatures.

C:\TEMP>a.exe
Hi!
ConsoleApplication4.C1
System.__ComObject
Inside C1=5
Inside C1=3

 

 

Thanks to Paul Harrington (a dev on the Visual Studio Platform team) and Misha Shneerson  for pointing me at the new functionality and code snippet for C1.GetInterface()  Misha has a lot more information about CLR V4 COM-interop advances on his blog at http://blogs.msdn.com/mshneer/.

 
Posted by jmstall | 0 Comments

Virtual code execution via IL interpretation

As Soma announced, we just shipped VS2010 Beta1. This includes dump debugging support for managed code and a very cool bonus feature tucked in there that I’ll blog about today.

Dump-debugging (aka post-mortem debugging) is very useful and a long-requested feature for managed code.  The downside is that with a dump-file, you don’t have a live process anymore, and so property-evaluation won’t work. That’s because property evaluation is implemented by hijacking a thread in the debuggee to run the function of interest, commonly a ToString() or property-getter. There’s no live thread to hijack in post-mortem debugging.

We have a mitigation for that in VS2010. In addition to loading the dump file, we can also interpret the IL opcodes of the function and simulate execution to show the results in the debugger.

 

Here, I’ll just blog about the end-user experience and some top-level points. I’ll save the technical drill down for future blogs.

Consider the following sample:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

public class Point 
{
    int m_x;
    int m_y;
    public Point(int x, int y)
    {
        m_x = x;
        m_y = y;
    }
    public override string ToString()
    {
        return String.Format("({0},{1})", this.X, this.Y);
    }

    public int X
    {
        get
        {
            return m_x;
        }
    }
    public int Y
    {
        get
        {
            return m_y;
        }
    }
}

public class Program
{
    static void Main(string[] args)
    {
        Dictionary<int, string> dict = new Dictionary<int, string>();
        dict[5] = "Five";
        dict[3] = "three";

        Point p = new Point(3, 4);
                
    }

    public static int  Dot(Point p1, Point p2)
    {
        int r2 = p1.X * p2.X + p1.Y * p2.Y;
        return r2;
    }

}

 

Suppose you have a dump-file from a thread stopped at the end of Main() (See newly added menu item “Debug | Save Dump As …”; load dump-file via “File | Open | File …”).

Normally, you could see the locals (dict, p) and their raw fields, but you wouldn’t be able to see the properties or ToString() values. So it would look something like this:

image

But with the interpreter, you can actually simulate execution. With the IL interpreter, here’s what it looks like in the watch window:

image

Which is exactly what you’d expect with live-debugging.  (In one sense, “everything still works like it worked before” is not a gratifying demo…)

The ‘*’ after the values are indications that they came from the interpreter.  Note you still need to ensure that property-evaluation is enabled in “Tools | options | Debugging”:

image

 

 

 

 

How does it work?
The Interpreter gets the raw IL opcodes via ICorDebug and then simulates execution of those opcodes. For example, when you inspect “p.X” in the watch window, the debugger can get the raw IL opcodes:

.method public hidebysig specialname instance int32
        get_X() cil managed
{
  // Code size       12 (0xc)
  .maxstack  1
  .locals init ([0] int32 CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldfld      int32 Point::m_x
  IL_0007:  stloc.0
  IL_0008:  br.s       IL_000a
  IL_000a:  ldloc.0
  IL_000b:  ret
} // end of method Point::get_X

And then translate that ldfld opcode into a ICorDebug field fetch the same way it would fetch “p.m_x”. The problem gets a lot harder then that (eg, how does it interpret a newobj instruction?) but that’s the basic idea.

 

Other things it can interpret:

The immediate window is also wired up to use the interpreter when dump-debugging. Here are some sample things that work. Again, note the ‘*’ means the results are in the interpreter and the debuggee is not modified.

Simulating new objects:
? new Point(10,12).ToString()
"(10,12)"*

Basic reflection:
? typeof(Point).FullName
"Point"*

Dynamic method invocation:
? typeof(Point).GetMethod("get_X").Invoke(new Point(6,7), null)
0x00000006*

Calling functions, and even mixing debuggee data (the local variable ‘p’) with interpreter generated data (via the ‘new’ expression):
? Dot(p,new Point(10,20))
110*

 

It even works for Visualizers

Notice that it can even load the Visualizer for the Dictionary (dict) and show you the contents as a pretty array view rather than just the raw view of buckets. Visualizers are their own dll,  and we can verify that the dll is not actually loaded into the debuggee. For example, the Dictionary visualizer dll is  Microsoft.VisualStudio.DebuggerVisualizers.dll, but that’s not in the module list:

image

 

That’s because the interpreter has virtualized loading the visualizer dll into its own “virtual interpreter” space and not the actual debuggee process space. That’s important because in a dump file, you can’t load a visualizer dll post-mortem.

 

 

Other issues:

There are lots of other details here that I’m skipping over, like:

  1. The interpreter is definitely not bullet proof. If it sees something it can’t interpreter (like a pinvoke or dangerous code), then it simply aborts the interpretation attempt.
  2. The intepreter is recursive, so it can handle functions that call other functions. (Notice that ToString call get_X.)
  3. How does it deal with side-effecting operations?
  4. How does it handle virtual dispatch call opcodes?
  5. How does it handle ecalls?
  6. How does it handle reflection

 

Other advantages?

There are other advantages of IL interpretation for function evaluation, mainly that it addresses the ”func-eval is evil” problems by essentially degenerating dangerous func-evals to safe field accesses.

  1. It is provably safe because it errs on the side of safety. The interpreter is completely non-invasive (it operates on a dump-file!).
  2. No accidentally executing dangerous code.
  3. Side-effect free func-evals. This is a natural consequence of it being non-invasive.
  4. Bullet proof func-eval abort.
  5. Bullet proof protection against recursive properties that stack-overflow.
  6. It allows func-eval to occur at places previously impossible, such as in dump-files, when the thread is in native code, retail code, or places where there is no thread to hijack.

Closing thoughts

We realize that the interpreter is definitely not perfect. That’s part of why we choose to have it active in dump-files but not replace func-eval in regular execution. For dump-file scenarios, it took something that would have been completely broken and made many things work. 

Posted by jmstall | 8 Comments
Filed under: ,

MVP Summit 2009

For those going to the 2009 MVP Summit, I’ll be one of the speakers at the breakout sessions on March 2nd on Microsoft’s Main campus.

Posted by jmstall | 0 Comments

Managed Dump debugging support for Visual Studio and ICorDebug

This is the longest I've gone without blogging, but our PDC announcements have stuff way too cool to stay quiet about.

If you saw PDC, you've head that the CLR Debugging API, ICorDebug, will support dump-debugging. This enables any ICorDebug-based debugger (including Visual Studio and MDbg) to debug dump-files of .NET applications. The coolness goes well beyond that, but dump-debugging is just the easiest feature to describe.

This was not an overnight feature, and required some major architectural changes to be plumbed through the entire system.  Specifically, when dump-debugging, there's no 'live' debuggee, so you can't rely on a helper-thread running in the debuggee process to service debugging requests anymore, so you need a completely different model.

Rick Byers has an excellent description of the ICorDebug re-architecture in CLR 4.0.  He also describes some of the other advancements in the CLR Tools API space. Go read them.

Posted by jmstall | 4 Comments
Filed under:

Updated MSDN forums

The MSDN forums are updated and have a new look and feel. It's at a new link too:  http://forums.msdn.microsoft.com/en-US/netfxtoolsdev/threads/  (the old link still forwards).

Posted by jmstall | 2 Comments

Stuff in Reflection that's not in Metadata

Previously, I mentioned some things in Metadata that aren't exposed in Reflection.  Here's an opposite case.

While metadata represents static bits on disk, Reflection operates in a live process with access to the CLR's loader. So reflection can represent things the CLR loader and type system may do that aren't captured in the metadata.

 

For example, an array T[] may implement interfaces, but which set of interfaces is not captured in the metadata. So consider the following code that prints out the interfaces an int[] implements:

using System;

class Foo
{
    static void Main()
    {
        Console.WriteLine("Hi");
        Console.WriteLine(Environment.Version);
        Type[] t2 = typeof(int[]).GetInterfaces();
        foreach (Type t in t2)
        {
            Console.WriteLine(t.FullName);
        }
        Console.WriteLine("done");
    }
}

Compile it once for v1.0. Then run the same binary against v1 and v2.  It's the same file (and therefore same metadata) in both cases.
The V1 case is inconsistent because it doesn't show any interfaces, it should at least show a few builtins like IEnumerable (typeof(IEnumerable).IsAssignableFrom(typeof(int[])) == True). But in the v2 case, you see reflection showing the interfaces, particularly the new 2.0 generic interfaces, that the CLR's type system added to the the array. So the V2 list is not the same as the V1 list, but this difference is not captured in the metadata.

 

C:\temp>c:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\csc.exe t.cs
Microsoft (R) Visual C# .NET Compiler version 7.10.6001.4
for Microsoft (R) .NET Framework version 1.1.4322
Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.


C:\temp>t.exe
Hi
1.1.4322.2407
done

C:\temp>set COMPLUS_VERSION=v2.0.50727

C:\temp>t.exe
Hi
2.0.50727.1433
System.ICloneable
System.Collections.IList
System.Collections.ICollection
System.Collections.IEnumerable
System.Collections.Generic.IList`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
System.Collections.Generic.ICollection`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
System.Collections.Generic.IEnumerable`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
done

Posted by jmstall | 0 Comments

Nice MSDN URLs

I noticed that MSDN finally has nice URLs for the BCL. (Or perhaps that should be "I finally noticed that ...", depending on how long this has been)

So instead of:

http://msdn.microsoft.com/en-us/library/1009fa28.aspx

You can do:

http://msdn.microsoft.com/en-us/library/system.reflection.assembly.loadfrom.aspx

For overloaded methods, it takes you to the disambiguation page.

This actually makes it a lot easier to find BCL APIs when you know the name. Now most of the time, I can just type the name right into the URL. And I have a higher confidence that this link won't get broken.

Posted by jmstall | 1 Comments

The price of complexity

My house was haunted. One of the lights would randomly go on or off and random times without anybody fiddling the switch.

The previous owner of our house had installed fancy dimmer light switches. On a whim, we replaced one of the fancy switches with a simple on/off switch.  As we took the old fancy switch out, we noticed that it had capacitors, a circuit board, resistors, even a microchip! That's a lot of different things to break down.

The new simple switch worked great, and the associated light no longer randomly toggles. So I conclude the fancy light switch was the problem.

Furthermore, the dimmer switches were more complicated to use because they had some fancy pressure touch thing: a quick fast press would toggle the light; a soft press would dim the light. It took us a while to get used to them.

When I was at Home Depot, the fancy switches were $40; whereas the simple on/off switches were ~$3.  The old simple switches throughout our house are still working; and we never really needed the dimming features for the fancy switches.

So the fancy switches were more expensive, harder to use, and haunted. Overall, not an ideal tradeoff for our needs. We'll stick with the simple on/off switches.

 

I think the same things applies to software design. You can build complicated software with lots of features, but that also means more places where it could break down. If a simple solution does the trick, it may not only be cheaper to build, but easier to use and cheaper to maintain in the long run.

Posted by jmstall | 4 Comments

Codegen for On Error Resume Next

VB has a "On Error Resume Next", which tells each line to swallow exceptions and just keep executing to the next line. It's kind of like a try-catch around every single line.

It may be tempting for C++ developers to make fun of VB for this, but this is a lot like programming with HRESULT (or any error handling strategy based on return codes) when you forget to check the return value. And as we look at the codegen below, VB's code could turn out to be even more efficient than the unmanaged error handling equivalent.

 

So in this VB snippet, the exception is is swallowed and "Next" is printed.

Module Module1

    Sub Main()
        On Error Resume Next
        Console.WriteLine("Hello!")
        Throw New Exception("Bong!")

        Console.WriteLine("Next")
    End Sub

End Module

 

If you're wondering what the codegen is like, you can always compile and then view the IL in ildasm. Or you can view it in Reflector as C#  instead of IL, which is easier to comprehend.

 

[STAThread]
public static void Main()
{
    // This item is obfuscated and can not be translated.
    int VB$ResumeTarget;
    try
    {
        int VB$CurrentStatement;
    Label_0001:
        ProjectData.ClearProjectError();
        int VB$ActiveHandler = -2;
    Label_0009:
        VB$CurrentStatement = 2;
        Console.WriteLine("Hello!");
    Label_0016:
        VB$CurrentStatement = 3;
        throw new Exception("Bong!");
    Label_0023:
        VB$CurrentStatement = 4;
        Console.WriteLine("Next");
        goto Label_009E;
    Label_0035:
        VB$ResumeTarget = 0;
        switch ((VB$ResumeTarget + 1))
        {
            case 1:
                goto Label_0001;

            case 2:
                goto Label_0009;

            case 3:
                goto Label_0016;

            case 4:
                goto Label_0023;

            case 5:
                goto Label_009E;

            default:
                goto Label_0093;
        }
    Label_0059:
        VB$ResumeTarget = VB$CurrentStatement;
        switch (((VB$ActiveHandler > -2) ? VB$ActiveHandler : 1))
        {
            case 0:
                goto Label_0093;

            case 1:
                goto Label_0035;
        }
    }
    catch (object obj1) when (?)
    {
        ProjectData.SetProjectError((Exception) obj1);
        goto Label_0059;
    }
Label_0093:
    throw ProjectData.CreateProjectError(-2146828237);
Label_009E:
    if (VB$ResumeTarget != 0)
    {
        ProjectData.ClearProjectError();
    }
}

 

 

So you can see it's just putting the entire region in a try/catch block, and then using a switch table to jump back to the appropriate line.  It has a "Current Statement" variable to track the last successful line to execute before an exception may have been thrown, and then switches to the next line on the exception path.

The switch table may seem evil at first, but remember that in native code, all those IfFailGoto() checks to propagate return results also add up to a lot of switching code. In this case, the branches are at least optimized into a single switch table as opposed to scattered branch code.

Posted by jmstall | 3 Comments

The waiting game

Punting on a problem can be good or bad, depending on the situation. Punting is not always retreating or surrendering.

Punting is good when the problem will be easier to solve later.

  1. For example, maybe you suspect something may happen that will render the problem moot (eg, a product would be discontinued). 
  2. Maybe you'll have more relevant information later that lets you inject some simplifying assumptions. 
  3. Maybe you'll have new technologies / options available later than make it easier to solve.
  4. Sometimes, some problems just work themselves out over time because the decisions to an ambiguous problem are answered by the decisions to a clear problem (see e^pi  = -1). 

 

Punting is bad when the problem gets harder to solver later.

  1. Maybe you're accumulating debt until you solve it. I find this is often the case with FxCop - I like to get FxCop enabled sooner rather than later because it's easier to stay at 0 errors once you get to 0 errors. 
  2. Debt accumulation also happens if future components will be broken until you fix some problem.
  3. Or maybe not having the problem solved is holding you hostage on solving other things.
  4. Maybe punting screws up your dependencies.
Posted by jmstall | 0 Comments

Understand the end-to-end scenarios

If you don't understand the end-to-end scenario, it's easy to do something that is ultimately self-defeating.

For example, my 3yr old daughter recently learned to play hide-and-seek. The goal of the game is to hide and avoid being found while the "it" player searches for you.

She's got the part about hiding down. But then once hidden, she shouts out "Daddy, come and find me!!". Given that I'm not deaf, it's kind of self-defeating.

 

But seriously...

This comes up often in performance scenarios. We've all heard some story where somebody tries a premature optimization and it ends up slowing things down. For example, somebody goes and adds caching to something, and the overhead of maintaining the cache drowns out any perf wins.

Another example is adding some cool feature that's an innate security hole.

A more general example is adding an feature X, but you need feature Y in order to use X.  For example, to create a new ICorDebug object in CLR V2, you needed to pass a version string of the debuggee. But V1 didn't have a way to get a version string from a running process, so we also had to add a 2nd new API, mscoree!GetVersionFromProcess in order to enable the attach scenarios.

Posted by jmstall | 1 Comments
Filed under:

Sometimes it's the obvious answer

Sometimes the answer to a question is so obvious that we skip over it looking for a fancier answer.

Example: A chair at my house had a bunch of little indentations on the seat - kind of like what you'd expect if somebody took a math compass and poked the chair a bunch of times.

I had some friends over and asked them what they thought caused the indentations. They guessed things like:

  • did you set something on top of it such as another chair?
  • did the buttons on somebody's pants scratch it?
  • did it get accidentally bumped a bunch of times?
  • did you drop a box of nails on it?

No. My 3 year old took a math compass and poked the chair a bunch of times.

 

But seriously ...
I noticed this with threading bugs. Sometimes I'd look at a crash dump of a race condition. I'd have no live repro and would be pouring over the sources trying to infer what the race could be. The search space for race conditions is huge, and I'd often find some crazy 15 step race ("the user presses 3 buttons within 10 ms while 4 threads happen to be at certain spots"). But while that was technically a bug, it turned out that was never the real problem. The real problem would always be much simpler.

Posted by jmstall | 1 Comments
Filed under:

Arguing by-example vs. by-principle

You can argue by providing examples supporting your case.  Alternatively, you can argue by appealing to more general principles.

For example, in arguing that "exposing public fields is bad," you could say:
By-principle: "It breaks abstraction and encapsulation."
By-example: "This untrusted plugin could set field m_foo to value 4 and cause a null-reference exception on line 16 of file widget.cs".

 

  By example By principle
When it's good Sometimes an example / counter-example can clearly illustrate a problem in an undeniable way. (example) Can rapidly prune the decision tree and avoid wasting time in overly specific discussions that would ultimately lead to a dead-end. 
Abstraction level low - uses specific data points high - uses generalizations
When it's bad This puts a burden on coming up with the "killer example", which you may not be able to do until it's too late. You can waste a lot of time trying to come up with specific examples. The inability to come up with a specific killer example can lead to a false sense of security. (Just because I can't find  a bug in your code does not mean it's bug-free.) Principles may be more subjective and may be more complicated to process.
Principles can be easily misapplied. (eg, "My principle is that our code should never crash; therefore I'm going to explicitly put null checks before every pointer access").
Myers-Briggs of target audience S (sensory) N (intuitive)
Posted by jmstall | 1 Comments
Filed under:
More Posts Next page »
 
Page view tracker