简体   繁体   中英

Generic Messaging Pattern in C#

I'm not sure if I can do this or not, but I am building the service layer of an application which needs to add one type of entity to another. An example might be adding an article to a category. It does this through a service class, for example:

public class ArticleService {
     public IResponse AddArticleToCategory(IAddRelatedItemRequest<Category, Article> request) {
     // do stuff with the request
     }
}

I would like the IAddRelatedItemRequest interface to be generic so it can be used for any addition request, similar to:

public interface IAddRelatedItemRequest<T, U>
where T : class
where U : class {
    Object Key { get;set; }
    List<Object> RelatedKey { get;set; }
}

What is happening is that the request requires the primary key of the item (eg the category) and a list of the primary keys of the related items (eg the articles). The AddCommentToArticle class in the concrete ArticleService then retrieves the item by its key and then adds the related key items to it.

(NB What I don't want to happen is supply the actual entity and a list of related entities - it needs to be done through primitive primary keys)

I want to strongly type the request somehow, so instead of an object and a list of objects being supplied, I can supply (for example) a Guid and a list of integers.

I don't particularly want to supply the two types through Generics as it makes for less readable code and the keys of the objects could potentially change type.

Ideally I would like to somehow extract the type of the entity's Id and include that in the interface. Is this possible?

I'm not quite sure I'm following your example completely, but it sounds like what you want is something like this:

interface IKeyResolver<T, TKey>
{
  TKey GetKey(T item);
}

public interface IAddRelatedItemRequest<TParentKey, TChildKey>
{
  TParentKey Key { get;set; }
  List<TChildKey> RelatedKey { get;set; }
}

// assume categories have an int key
class CategoryKeyResolver : IKeyResolver<int>
{
  int GetKey(Category c) { return c.CategoryId; }
}

// assume articles use a GUID
class ArticleKeyResolver : IKeyResolver<Guid>
{
  Guid GetKey(Article a) { return a.ArticleId;
}

You would then use the appropriate key resolvers in your service methods. The key resolvers could be properties in your service, or just instantiate the appropriate key resolver as needed. Something like a key resolver is really useful when your underlying persistence mechanism is itself generic (eg a generic Repository implementation).

The C# compiler can infer the types for you - given at least one place where they are defined. Firstly, you can't use where T : class because int and Guid are not reference types. Therefore your interface definition would look like this:

public interface IAddRelatedItemRequest<T, U>
{
    T Key { get; set; }
    IList<U> RelatedKey { get; set; }
} 

You would then implement it. This would be your central 'control point' for the types of the keys:

public class SomeItemRequest : IAddRelatedItemRequest<int, int>
{
}

Make your method generic and the C# compiler can infer the types for you:

public static IResponse AddArticleToCategory<T, U>(IAddRelatedItemRequest<T, U> request)
{
    return null;
}
// Usage:
ArticleService.AddArticleToCategory(new SomeItemRequest());

I believe that usage example is what you are after.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM