Josh
Josh

Reputation: 8477

Unity configuration mapping with generics

I have Class and Interface that use generics. We use configuration files to manage mappings with Microsoft Unity for DI.

Class:

namespace Acme.Core
{    
  public class CommonCache<T> : ICommonCache<T>
  {
    private string _cacheKey;

    public CommonCache(string cacheKey)
    {
        _cacheKey = cacheKey;
    }

    public IReadOnlyList<T> GetAll(List<T> dataList)
    {
       // Code returns IReadOnlyList<T>
    }
}   

Interface:

namespace Acme.Core.Interfaces
{
    public interface ICommonCache<T>
    {
        IReadOnlyList<T> GetAll(List<T> dataList);
    }
}

What I was hoping for was something like this:

 <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <assembly name="Acme.Core" />
    <namespace name="Acme.Core" />
    <namespace name="Acme.Core.Interfaces" />

   <container name="Default">
    <register type="ICommonCache[*]" mapTo="CommonCache[*]">
      <constructor>
       <param name="cacheKey" value="*" />
      </constructor>
    </register>
   </container>
  </unity>

I know * is not proper syntax but my goal is to allow any type to be passed for the type and mapTo for the generic type. For the constructor I'd like to pass the value for the cacheKey parameter, I have value="*" to illustrate the goal of passing any value.

What is the correct syntax for this mapping to work?

Upvotes: 0

Views: 1354

Answers (2)

Nkosi
Nkosi

Reputation: 247058

According to documentation about

Specifying Types in the Configuration File.

Generic Types

The CLR type name syntax for generic types is extremely verbose, and it also does not allow for things like aliases. The Unity configuration system allows for a shorthand syntax for generic types that also allows for aliases and type searching.

To specify a closed generic type, you provide the type name followed by the type parameters in a comma-separated list in square brackets.

The Unity shorthand would look like the following example. XML

<container>
    <register type="IDictionary[string,int]" </register>
</container>

If you wish to use an assembly name-qualified type as a type parameter, rather than an alias or an automatically found type, you must place that entire name in square brackets, as shown in the following example: XML

<register type="IDictionary[string, [MyApp.Interfaces.ILogger, MyApp]]"/>

To specify an open generic type you simply leave out the type parameters. You have two options:

  • Use the CLR notation of `N where N is the number of generic parameters.

  • Use the square brackets, with commas, to indicate the number of generic parameters.

    Generic Type | Configuration file XML using CLR notation | Configuration file XML using comma notation

    IList => IList`1 => IList[]

    IDictionary => IDictionary`2 => IDictionary[,]

Upvotes: 0

TylerOhlsen
TylerOhlsen

Reputation: 5578

You can accomplish something like what you are after by first registering the open generic type and then (if you wish) override that registration with closed generics.

First add a default constructor to implement the behavior you wish when no closed generic registration is present...

public CommonCache()
{
    _cacheKey = typeof(T).FullName;
}

Open Generics Registration

<register type="ICommonCache`1" mapTo="CommonCache`1">
  <!-- Use the default constructor -->
  <constructor />
</register>

Close Generics Registration(s)

<register type="ICommonCache`1[BusinessType]" mapTo="CommonCache`1[BusinessType]">
  <constructor>
    <param name="cacheKey" value="BusinessTypeCacheKeyOverride" />
  </constructor>
</register>

But I would recommend you instead remove value types from your constructor. This could be done by introducing a new class as a dependency to set up the type to cache key map. That could in turn read from config.

public interface ICacheKeyMap
{
    string GetCacheKey(Type t);
    void SetCacheKey(Type t, string cacheKey);
}

Upvotes: 1

Related Questions