Major take-aways from ScrumMaster course

I took my ScrumMaster course three weeks ago and had the honour to take it with Jeff Sutherland as our teacher. This course was very insightful – a pity that it was so short! So I guess 90% of my learnings are hidden in the slides and in the back of my mind. But I’d like to write down and share with you some of the major takeaways.

What it is all about

Scrum is like a bootstrap program in a computer (BIOS). When you start Scrum it seems all chaotic (and it is). But by self-organizing and applying the rules it improves massively. However, if you change a bit in the bootstrap program, the computer does not work anymore.

The setup of Scrum aims in making an average team a very high-performing team. If the velocity of the team does not increase steadly throughout the sprints, that indicates that Scrum was not fully implemented.

According to Jeff, many Scrum teams have improved their performance by about 50%, which is fine. But it is not what Scrum was designed for. If your performance does not improve by about 300-400% after introducing Scrum, you are not implementing it right.

Both Wikispeed and Toyota can build a new car prototype every single week – they are both applying Scrum.

Steadily improving team performance

There are a few things that have proven to heavily increase performance:

  • Introducing (and sticking to) a proper Definition of Done (DoD) can double your velocity.
    (DoD defines criteria that have to be fulfilled by a freshly implemented backlog item to be considered “done”)
  • Introducing a proper Definition of Ready can double your velocity, too (resulting in a factor of 4 with the DoD)
    (DoR defines criteria that have to be fullfilled by a new backlog item before it can be taken into a sprint, e.g.: “Must follow I.N.V.E.S.T. criteria” + “Most of the team understand the item” + “The team has an initial idea for the architecture”)
  • Morale is a performance multiplier. The PO is critical to motivate the team with the project vision.

Here are a few performance killers.

  • The biggest waste in software development is incompleted work being taken over to the next sprint (not tested, technical debt…)
  • If you test a feature in the subsequent sprint which would have taken 1h to be tested in the current sprint, it can potentially take up to 24h instead.
  • “At midnight a bug turns into a feature” – cited by Jeff, unfortunately I am not able to find the original on google. If you find a bug (originating from the current sprint’s development), fix it immediately or it will we cost you a lot more. A bug not fixed within 9 hours is a good indication for the ScrumMaster that there is an impediment.

Roles and Meetings

Some new things I learnt about Scrum roles and meetings.

ScrumMaster

  • It is the ScrumMaster’s responsibility to maximize the team’s performance.
  • This means, the ScrumMaster is not simply required to teach the team how to do Scrum (I heard people say that experienced teams don’t need a ScrumMaster because they know how to do Scrum). The ScrumMaster’s duty is that the team performance improves in every Sprint.
  • If the ScrumMaster participates in programming, he adds some value but loses focus on improving the team’s performance.

Product Owner

  • The Product Owner (mainly) definies the Definition of Done! (I was thinking that the team does it.)
  • The PO must promote the vision of the project to motivate the team – Morale is a performance booster.
  • The PO plans the releases and sets either their scope or their date.
  • The PO (or someone from the PO team) must do the final acceptance test. Only he can move a backlog item to done.

Retrospective

  • The goal of the retrospective is to increase velocity. If velocity is not measured and inspected, the team cannot judge whether it improved over the last sprints. This is the main point of using story points for estimating user stories!
  • Only one action should result from a retrospective – only change one thing per sprint, but focus on really doing it.

Backlog refinement

  • It has proven useful to have a regular meeting for refining the top backlog items and make them “ready” according to the DoR – for example twice a week, discussing one or two stories each time.
  • It is not necessary that the whole backlog be estimated. Only estimate as many stories as are required by the Product Owner for release planning (and of course any item that will go into the next sprint).

Release Planning

  • By estimating the business value of each backlog item and ordering them properly, the items build a continuously increasing value-over-effort curve that starts off very steep.
  • The PO can maximize the value of the first release by planning it at the point where the curve turns from steep to flat.
  • The remaining backlog items sum up to only little value added with much effort. This is the value that would be added if the project was planned and executed as a whole.
  • The feedback from the first release enables the PO to create, modify and re-estimate items, resulting in a much greater value for the second release (and any subsequent releases equally).

Testing and Testers

  • Anybody can test – except the developer of the feature.
  • Final testing must be done by the PO (or anybody from his PO team)
  • Testing should not be made a state (“todo – in progress – testing – done”). Better make it a task.

Using Eric Hynds’ multiselect with knockout

Eric Hynds has crafted an awesome enhanced multiselection dropdown as a jQuery UI widget which enhances a normal <select multiple> list. Unfortunately, as many jQuery UI widgets, it manipulates the original HTML when being applied, which renders the normal use of the <select> knockout data-bindings in-effective.

Classical knockout binding for multiselect dropdowns:

<select multiple="multiple" data-bind="
    options: data.selectList,
    optionsText: 'Name',
    optionsValue: 'Value',
    selectedOptions: data.selectedOptions">

This select HTML element is replaced by the UI widget. The user then interacts with a set of buttons, divs, checkboxes, and more.

However, the good news to that is that the UI widget is designed such as to simply hide rather than removing the original element. Even better, when the user selects items in the UI widget, they also get selected in the original <select> element. Therefore with a little trick, we can make use of the knockout bindings for the original select list.

Simply add “multiselect: true” to the data-bind attribute:

<select multiple="multiple" data-bind="
    options: data.selectList,
    optionsText: 'Name',
    optionsValue: 'Value',
    selectedOptions: data.selectedOptions,
    multiselect: true">

We then define a new custom knockout binding called “multiselect”:

ko.bindingHandlers.multiselect = {
    init: function (element) {
        $(element).bind("multiselectclick", function () {
            $.data(element, 'donotrefresh', true);
        });
    },
    update: function (element) {
        var doNotRefresh = !!($.data(element, 'donotrefresh'));
        if (!doNotRefresh) { $(element).multiselect("refresh"); }
        $.data(element, 'donotrefresh', false);
    }
};

The core part to this is the $(element).multiselect(“refresh”), which causes the UI widget to re-build its select list based on the hidden original <select> list, which in turn is updated by knockout based on the model.

The other direction (view -> model) is covered by the UI widget itself: The widget updates the hidden select list, knockout consequently updates the model. There is one minor caveat though: The model update in turn triggers the “update” method of our custom binding. In this case, if we tried and called “refresh” on the UI widget, an error would occur. In order to prevent that, we hook into the click event, set a flag called “donotrefresh” and check it before calling “refresh”.

This way, the “refresh” method on the UI widget is only called if the model was changed from the “outside”, yet is not called if the model was changed due to user interaction with the widget.

Two Useful Classes When Using Excel COM Interop

There are two known problems when working with Excel Interop.

  1. After having used Excel and shut down your application, an Excel process still “hangs” in background.
  2. When your Excel installation is English (en-US) only, but your Windows is configured for a different language (e.g. de-DE), some calls to the Interop fail with a COMException.

Luckily there are also workarounds for this problem which you can easily find when googling for them. The following classes have been handy for me to encapsulate these workarounds. Both providing IDisposable.

This way, you can use them as follows:

Excel.Workbooks myWorkbooks
        = myExcelApplication.Workbooks;
Excel.Workbook myWorkbook;

using (new Disposer(myWorkbooks))
    using (new EnUsCulture())
        myWorkbook = myWorkbooks.Open(fileName, [...]);

The Disposer takes care of properly disposing of the COM object. The EnUsCulture switches the current thread’s culture to en-US right on creation. On Dispose(), it switches the culture back to what it was before. Using “using” you make sure the thread’s culture is switched back even if an error occurs.

Disposer:

    public class Disposer : IDisposable
    {
        private object _comObject;

        public Disposer(object comObject)
        {
            _comObject = comObject;
        }

        public void Dispose()
        {
            if (_comObject != null)
            {
                Marshal.ReleaseComObject(_comObject);
                _comObject = null;
                GC.Collect();
            }
        }
    }

 
EnUsCulture:

    public class EnUsCulture : IDisposable
    {
        private CultureInfo _oldCulture;

        public EnUsCulture()
        {
            if (Thread.CurrentThread.CurrentCulture
                .Equals(new CultureInfo("en-US")))
            {
                _oldCulture = null;
            }
            else
            {
                _oldCulture = Thread.CurrentThread.CurrentCulture;
                Thread.CurrentThread.CurrentCulture
                    = new CultureInfo("en-US");
            }
        }

        public void Dispose()
        {
            if (_oldCulture != null)
                Thread.CurrentThread.CurrentCulture = _oldCulture;
        }
    }

Note that the Dispose() method only resets the thread’s culture if it wasn’t already en-US before. This is to prevent the following situation:

    EnUsCulture c1 = new EnUsCulture(); // switch from de-DE to en-US
    EnUsCulture c2 = new EnUsCulture(); // switch from en-US to en-US
    c1.Dispose(); // switch back to de-DE
    c2.Dispose(); // switch back to en-US

The thread would end up with the en-US culture if c2 is disposed after c1. To prevent that, we make c2 notice on its creation that the culture has already been switched before, so it won’t have any effect at all:

    EnUsCulture c1 = new EnUsCulture(); // switch from de-DE to en-US
    EnUsCulture c2 = new EnUsCulture(); // no effect
    c1.Dispose(); // switch back to de-DE
    c2.Dispose(); // no effect

Calling an Oracle Stored Proc with CLOB from System.Data.OracleClient

You might have stumbled upon this and it might have made you tear your hair just as it did to me. When you try to call a stored procedure from .NET using System.Data.OracleClient, you won’t be able to pass a CLOB parameter value > 4000 Bytes.

I got the following error message when trying to do so:

ORA-01460 – unimplemented or unreasonable conversion requested

This issue has been reported in lots of places on the web already. Unfortunately I wasn’t able to find the right hint to remedy this problem – until a fellow employee of mine saved my day!

In fact, the solution, or rather workaround, is quite simple and can be found on http://henbo.spaces.live.com/blog/cns!2E073207A544E12!332.entry:

Set the System.Data.OracleClient.OracleParameter.OracleDbType property to System.Data.OracleClient.OracleType.Clob.
– AND –
Set the parameter value when BeginTransaction has already been called on the DbConnection.

Don’t let the “OracleDbType” (implying that the guy speaks about Oracle’s version of the client) derange you: The trick works for the Microsoft client just as well!

Creating an excel-like grid for ASP.NET

I have been searching around for a while because I was in the need for an ASP.NET control similar to asp:GridView, but with following huge differences:

  • All rows and their cells must be in edit mode all the time. On postback (e.g. using a “Save” button), the data must be written back to the datasource.
    In GridView, in contrast, you need to hit a row’s  “Edit” to turn the row into edit mode, and hit the row’s “Update” button to save its changes to the datasource.
  • You should be able to navigate through the cells using the arrow keys.
    In GridView, this doesn’t even apply since only one row at a time is in edit mode.
  • You should be able to copy-paste multiple cells at a time from Excel into the grid. If you copy 3×2 cells in Excel, paste the cursor on a cell in the grid and Hit Ctrl+V, the cell values must be spread over the cells below and right from the selected cell.
    In GridView, in contrast, all cell values are pasted as a concatenated string into the selected cell.
  • The grid should work in IE, Firefox and possibly other browsers. Well, I have to admit that my solution presented here only works for IE so far, but it should be feasible to extend the code for Firefox and Safari.

Having searched for quite a long time, the only really interesting thing I found was Srikanth Reddy’s Blog “Creating an Excel Like GridView”. His solution was really close to what I needed, and I want to thank Srikanth in this place for sharing his work. The original blog is not available anymore, but there now is a copy of Srikanth’s post at: http://rschandrastechblog.blogspot.com/2010/11/client-gridview-iii-httpwwwaspboycomcat.html. Yet I decided to post the whole of my code (which is mostly to be attributed to Srikanth) in this blog.

His version didn’t support pasting cells either, but it was quite easy to extend it such that this is now supported, too.

Apart from the new features, I also changed the structure of the code a bit. Let’s have closer look at the new code:

1. General Methods for Navigation

  • There are 4 methods “getLeftNeighbor”, “getRightNeighbor”, “getAboveNeighbor” and “getBelowNeighbor”. They find and return the cell that is found right next to the given one.
  • The code to actually select a certain cell is found in “NavigateToCell”.
  • The code to format the previously selected and newly selected cells is found in the onfocus event. It’s the best place to do the formatting becausee it will take place no matter whether you click a cell, or use the arrows, tab or shift-tab to navigate to a neighbored cell.

2. Intercepting the paste event and spreading multiple values over the grid

When the user copies multiple cells from an Excel sheet and pastes it in a cell, you’d expect the values to be spread over multiple rows and columns, just like they would in Excel. However, the default behavior of a HTML textbox is to regard the string as a single value and paste everything in that particular text box.

Suppose that the user copies the following 3 x 2 cells from excel:

a b
c d
e f

These values are represented in the clipboard as a text formatted as “a\tb\r\nc\td\r\ne\tf\r\n“.

In order to have these values be spread over the appropriate text boxes we need to intercept the paste event, split the clipboard text into a 2-dimensional array of values and then reuse our get-neighbor methods to find the cells below and right from the selected cell:

    function tupelizeText(text) {
        text = text.replace(/\r\n/g, '\n');
        text = text.replace(/\r/g, '\n');

        var tmparray = text.split("\n");
        var result = new Array();

        for (var i = 0; i < tmparray.length - 1; i++) {
            result[i] = tmparray[i].split('\t');
        }

        return result;
    }

    function txt_paste(element) {
        var strPasteData = window.clipboardData.getData("Text");
        var values = tupelizeText(strPasteData);
        var leftAreaBorderCell = curCell;
        for (i = 0; i < values.length; i++) {
            var pasteCell = leftAreaBorderCell;
            for (j = 0; j < values[i].length; j++) {
                pasteCell.firstChild.innerText = values[i][j];

                if (getRightNeighbor(pasteCell) != null)
                    pasteCell = getRightNeighbor(pasteCell);
                else
                    break;
            }

            if (getLowerNeighbor(leftAreaBorderCell) != null)
                leftAreaBorderCell = getLowerNeighbor(leftAreaBorderCell);
            else
                break;
        }

        return false;
    }

Imagine the special case where the selection in our grid is placed such that not all values from the clipboard can be pasted. To cover this we always check whether there is another row below or another column at the right. If not, we break that particular iteration.

To register the onpaste event I added a line in the C# code. I also added the onfocus event and a css class:

txt.Attributes.Add("onfocus", "txt_focus(this)");
txt.Attributes.Add("onpaste", "return txt_paste(this)");
txt.Attributes.Add("class", "SpreadsheetView-TextBox");

So much about some particularities. I don’t (yet) take the effort to explain the parts that Srikanth already introduced… I trust in your own development skills 🙂 So as promised, the full code below.

I hope this inspires you. If there are things unclear, please let me know! And again, much credits to Srikanth Reddy for his post. If you improve or extend this code (e.g. make it work in Firefox and/or Safari), please let me know! If you publish this code or an adaption of it, please attribute and put a link on this blog post.

SpreadsheetView.ascx

<%@ Control Language="C#" AutoEventWireup="true"
CodeBehind="SpreadsheetView.ascx.cs"
Inherits="WebBased.SpreadsheetView" %>

<div id="divSpreadsheetView" onkeydown="divSpreadsheetView_keydown(event)">
<table ID="tblTable" cellspacing="0" cellpadding="0" runat="server">
<tr>
<td><input type="text" /></td>
<td><input type="text" /></td>
</tr>
<tr>
<td><input type="text" /></td>
<td><input type="text" /></td>
</tr>
</table>
</div>

<script language="javascript" type="text/javascript">

var curCell = null;

// Called when user clicks on the textbox of a SpreadsheetView cell, selects the cell.
function txt_focus(element) {
if (curCell != null) curCell.className = "SpreadsheetView-Cell";
curCell = element.parentNode;
curCell.className = "SpreadsheetView-SelectedCell";
curCell.firstChild.select();
}

// Returns the cell left from the given cell, or null if the given cell is the left-most.
function getLeftNeighbor(cell) {
if (cell.previousSibling != null) {
return cell.previousSibling;
} else {
return null;
}
}

// Returns the cell right from the given cell, or null if the given cell is the right-most.
function getRightNeighbor(cell) {
if (cell.nextSibling != null) {
return cell.nextSibling;
} else {
return null;
}
}

// Returns the cell above the given cell, or null if the given cell is the top-most (excluding header).
function getUpperNeighbor(cell) {
if (cell.parentNode.previousSibling.rowIndex != 0) {
return cell.parentNode.previousSibling.children[cell.cellIndex];
} else {
return null;
}
}

// Returns the cell below the given cell, or null if the given cell is the bottom-most.
function getLowerNeighbor(cell) {
if (cell.parentNode.nextSibling != null) {
return cell.parentNode.nextSibling.children[cell.cellIndex];
} else {
return null;
}
}

// Helper method to select a cell.
function NavigateToCell(cell) {
cell.firstChild.focus();
}

// Called when user hits an arrow key while focus is on the SpreadsheetView, selects a neighbor cell.
function divSpreadsheetView_keydown(event) {
var keyCode;

if (!event) event = window.event;

if (event.which) {
keyCode = event.which;
} else if (event.keyCode) {
keyCode = event.keyCode;
}

if (keyCode == 37 && getLeftNeighbor(curCell) != null) {
NavigateToCell(getLeftNeighbor(curCell));
}
else if (keyCode == 38 && getUpperNeighbor(curCell) != null) {
NavigateToCell(getUpperNeighbor(curCell));
}
else if (keyCode == 39 && getRightNeighbor(curCell) != null) {
NavigateToCell(getRightNeighbor(curCell));
}
else if (keyCode == 40 && getLowerNeighbor(curCell) != null) {
NavigateToCell(getLowerNeighbor(curCell));
}
}

// Register keydown event.
//document.getElementById("divSpreadsheetView").onkeydown = divSpreadsheetView_keydown;

// Parses a string containing a copy of multiple excel cells and returns the cells in an array of arrays.
function tupelizeText(text) {
text = text.replace(/\r\n/g, '\n');
text = text.replace(/\r/g, '\n');

var tmparray = text.split("\n");

var result = new Array();

// text copied from excel always ends with a \n too much, so ignore last element.
for (var i = 0; i < tmparray.length - 1; i++) {
result[i] = tmparray[i].split('\t');
}

return result;
}

// Called when user pasts text in a textbox. If consists of multiple cells, spreads the values among textboxes.
function txt_paste(element) {
var strPasteData = window.clipboardData.getData("Text");

if (curCell == element.parentNode) {
var values = tupelizeText(strPasteData);

// this variable will iterate down the rows but always be the left-most of the area to paste into
var leftAreaBorderCell = curCell;

for (i = 0; i < values.length; i++) {

// this variable will iterate through all cells of the row
var pasteCell = leftAreaBorderCell;

for (j = 0; j < values[i].length; j++) {
pasteCell.firstChild.innerText = values[i][j];

if (getRightNeighbor(pasteCell) != null) {
pasteCell = getRightNeighbor(pasteCell);
} else {
break;
}
}

if (getLowerNeighbor(leftAreaBorderCell) != null) {
leftAreaBorderCell = getLowerNeighbor(leftAreaBorderCell);
} else {
break;
}
}

return false;
}

return true;
}

</script>

SpreadsheetView.ascx.cs

using System;
using System.Web.UI.WebControls;
using System.Data;
using System.Web.UI.HtmlControls;
using System.Web.UI;

namespace WebBased
{
public partial class SpreadsheetView : System.Web.UI.UserControl
{
public DataTable Model { get; set; }

public override void DataBind()
{
tblTable.Rows.Clear();
GenerateHeaderRow(tblTable, Model);

for (int iRow = 0; iRow < Model.Rows.Count; iRow++)
{
GenerateDataRow(tblTable, iRow, Model);
}
}

protected void Page_Load(object sender, EventArgs e)
{
if (this.IsPostBack)
{
// Read data from UI back into DataTable
for (int iRow = 1; iRow < tblTable.Rows.Count; iRow++)
{
for (int iCol = 0; iCol < tblTable.Rows[iRow].Cells.Count; iCol++)
{
HtmlTableCell cell = tblTable.Rows[iRow].Cells[iCol];
TextBox txt = cell.Controls[0] as TextBox;
Model.Rows[iRow - 1][iCol] = txt.Text;
}
}
}
}

private static void GenerateDataRow(HtmlTable tblTable, int iRow, DataTable dtDataTable)
{
HtmlTableRow row = new HtmlTableRow();
tblTable.Rows.Add(row);
row.Attributes.Add("class", "SpreadsheetView-Row");

for (int iCol = 0; iCol < dtDataTable.Columns.Count; iCol++)
{
HtmlTableCell cell = new HtmlTableCell();
row.Cells.Add(cell);
cell.Attributes.Add("class", "SpreadsheetView-Cell");

TextBox txt = new TextBox();
txt.ID = String.Format("txt_{0}_{1}", iRow, iCol);
txt.Width = Unit.Percentage(100);
txt.Text = dtDataTable.Rows[iRow][iCol].ToString();
txt.Attributes.Add("onfocus", "txt_focus(this)");
txt.Attributes.Add("onpaste", "return txt_paste(this)");
txt.Attributes.Add("class", "SpreadsheetView-TextBox");

cell.Controls.Add(txt);
}
}

private static void GenerateHeaderRow(HtmlTable tblTable, DataTable dtDataTable)
{
HtmlTableRow rowHeader = new HtmlTableRow();
tblTable.Rows.Add(rowHeader);
rowHeader.Attributes.Add("class", "SpreadsheetView-HeaderRow");

for (int iCol = 0; iCol < dtDataTable.Columns.Count; iCol++)
{
HtmlTableCell cellHeader = new HtmlTableCell("th");
rowHeader.Cells.Add(cellHeader);
cellHeader.Attributes.Add("class", "SpreadsheetView-HeaderCell");

cellHeader.InnerText = dtDataTable.Columns[iCol].Caption;
}
}

}
}

Putting it into Action

To use the control, just add a tag to your asp.net code:

<%@ Register TagPrefix="x" TagName="SpreadsheetView" Src="~/SpreadsheetView.ascx" %>

<x:SpreadsheetView ID="SpreadsheetView1" runat="server"/>

and feed it with an empty grid of your required size:

DataTable dataTable = new DataTable();

dataTable.Columns.Add("A");
dataTable.Columns.Add("B");
dataTable.Columns.Add("C");
dataTable.Columns.Add("D");

dataTable.Rows.Add(new string[] { "", "", "", "" });
dataTable.Rows.Add(new string[] { "", "", "", "" });
dataTable.Rows.Add(new string[] { "", "", "", "" });
dataTable.Rows.Add(new string[] { "", "", "", "" });
dataTable.Rows.Add(new string[] { "", "", "", "" });

SpreadsheetView1.Model = dataTable;
SpreadsheetView1.DataBind();

For completeness, these are some of the resources I found before trying it myself:

SharePoint traps when developing for multiple languages

I’ve just been programming some SharePoint extensions for a company running a german SharePoint (MOSS 2007) site, and I was astonished/amazed/annoyed by the fact that some parts of the SharePoint object model are heavily language-dependent. I mean such things as that the “official”, compile-time, property “Title” of a ListItem does simply not work in a german site collection, because it relies on the display name of the corresponding column.

This is why I will list some points what you should beware of if you want your thing to run in other languages than 1033:

  • Do not use ListItem.Title – as mentioned above. If you use ListItem.Title, the property (IMHO) searches the ListItem for a field whose display name is “Title” and returns its value. I used that to access the “TemplateTitle” of a site template in the site templates gallery. In a german environment that column has “Titel” as its display-name, thus the property “Title” throws a RuntimeException. Instead, I used ListItem.GetFormattedValue(“TemplateTitle”), which is the internal column name which does not depend on language and cannot be changed by a user.
  • Do not use “Full Control”, “Contribute”, … to refer to certain role definitions – I tried that, already sensing it might lead to troubles in a german environment. Of course, the role definitions are called “Vollzugriff”, etc. Thus, if you want to get a RoleDefinition object for “Full Control” / “Vollzugriff”, beware of using Web.RoleDefinitions[“Full Control”]. Instead, use the SPRoleType constants:  Web.RoleDefinitions.GetByType(SPRoleType.Administrator)
  • Do not use ~site/Pages/default.aspx as a hardcoded string – You guess it: In a german site collection, the thing is called ~site/Seiten/default.aspx. Besides, noone prevents a user from changing that URL in the document library’s settings. In WSS3, the default.aspx does not even reside in a document library but in the root path of the subsite: ~site/default.aspx. I have to admit that I’ve not yet discovered the correct way to get a site’s default.aspx page.
  • I will expand this post as soon as i’ll stumble over another language hurdle.

Enterprise Architect: Beware of for-each loops!

While developing a C#-based add-in for Sparx Enterprise Architect, I just encountered a very nasty bug:

System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at VariantClear(tagVARIANT* pvarg)
at System.Runtime.InteropServices.CustomMarshalers
.EnumeratorViewOfEnumVariant.MoveNext()
at Foo.bar()

The exception only arises when I deploy the add-in as a compiled (Release build) DLL. When debugging the add-in from within Visual Studio, it worked fine. And this is the “evil” code in Foo.bar():

foreach (EA.Element element in package.Elements)
{
    doSomething();
}

A second glance to the exception stack trace and a reading of this thread made me clear that the translation of “foreach” to a COM operation fails in that specific case. Use the following workaround to get rid of the issue:

for (short i = 0; i < package.Elements.Count; i++)
{
    EA.Element element =
        (EA.Element) package.Elements.GetAt(i);
    doSomething();
}

Much thanks to R Henry who had the same problem and posted his findings in the above-mentioned thread!