views:

46

answers:

2

We've got a generic class which contains a private class which uses the outer class's generic type parameter. Example:

public class Foo<T>
{
  private class Bar : IGrouping<Guid,T>{


  }
}

The problem is that we get a compile error in a unit test project which states

"The Type or method has 2 generic parameter(s), but 1 generic argument(s) were provided. A generic argument must be provided for each generic parameter."
I cranked up MSBuild verbosity (Tools > Options > Projects and Solutions > Build and Run). Looking at the error location, it points to the Microsoft.TeamTest.targets file. I was able to narrow it down to this class structure above. If I replace the private class Bar with concrete generic parameters in the class definition (see below), the unit test compiles fine:

private class Bar : IGrouping<Guid,int>{

}

I need to provide that second generic parameter generic to accept whatever the type is in the outter class. Thoughts on this one?

To clarify, the compile error is in the Unit Test Project, not the code listed above (which compiles fine). Perhaps the private accessor generation is choking in the MS unit test infrastructure?

Here's [almost] the entire actual code:

public class CategoryStorageService<T> : StorageServiceBase<T>, ICategoryStorageService<T> where T : IStorageContent, new()
{
    /// <summary>
    /// Returns a nested object graph of category-items
    /// </summary>
    /// <param name="categories"></param>
    /// <returns></returns>
    public IEnumerable<IGrouping<StorageCategory, T>> GetContent(params StorageCategory[] categories)
    {
        var results = this.GetContent(categories.Select(c => c.ID).ToArray());
        foreach (var result in results)
        {
            yield return new LocalNameGrouping(
                categories.First(c => c.ID == result.Key.ID),
                result);
        }
    }

    /// <summary>
    /// Returns a nested object graph of categories, each containing their items
    /// </summary>
    /// <param name="categories">The ID(s) of the category(ies) to fetch</param>
    /// <returns></returns>
    private IEnumerable<IGrouping<StorageCategory, T>> GetContent(params Guid[] categories)
    {

        // implementation removed

        return null;
    }      

    private class LocalNameGrouping : IGrouping<StorageCategory, T>
    {
        private IEnumerable<T> items;
        private StorageCategory category;

        public LocalNameGrouping(StorageCategory localCategory, IGrouping<StorageCategory, T> items)
        {
            this.items = items;
            string name = string.IsNullOrEmpty(localCategory.DisplayName) ? items.Key.DisplayName : localCategory.DisplayName;
            this.category = new StorageCategory { DisplayName = name, ID = items.Key.ID };
        }

        #region IGrouping<StorageCategory,T> Members

        public StorageCategory Key
        {
            get { return category; }
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return items.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }
A: 

Unfortunately I haven't been able to repro the problem. I filled out your sample with the code below and it compiles just fine in Visual Studio 2010.

public class StorageServiceBase<T> { }
public interface ICategoryStorageService<T> { }
public interface IStorageContent { }
public class StorageCategory { }
public interface IGrouping<T1, T2> : IEnumerable<T2> { }

public class CategoryStorageService<T> : StorageServiceBase<T>, ICategoryStorageService<T> where T : IStorageContent, new() {
    public IEnumerable<IGrouping<StorageCategory, T>> GetContent(params StorageCategory[] categories) {
        return null;
    }
    private IEnumerable<IGrouping<StorageCategory, T>> GetContent(params Guid[] categories) {
        return null;
    }

    private class LocalNameGrouping : IGrouping<StorageCategory, T> {
        private IEnumerable<T> items = null;
        private StorageCategory category = null;

        public LocalNameGrouping(StorageCategory localCategory, IGrouping<StorageCategory, T> items) {
        }

        #region IGrouping<StorageCategory,T> Members

        public StorageCategory Key {
            get { return category; }
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator() {
            return items.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return this.GetEnumerator();
        }

        #endregion
    }
}
JaredPar
@JaredPar but if you create a private accessor for that assembly, does the unit test project also compile? That is the issue.
Rex M
@Rex, then I would say it's almost certainly a bug in private accessors. Is this VS 2005 or VS 2008+ (very different implementations)
JaredPar
@JaredPar VS2008
Rex M
@Rex, sounds very much like a bug. Could you file it on connect? http://connect.microsoft.com/
JaredPar
+1  A: 

So the private accessor processing was angry at a type that had partially constructed generic class definitions. For example, it failed if the class was declared like this:

private class LocalNameGrouping : IGrouping<StorageCategory, T> { 

}

However, it was happy with this, which still satisfied the need (albeit a tad cheesy):

private class LocalNameGrouping<K> : IGrouping<K, T> where K: StorageCategory, new()
{

}
Jason