简体   繁体   中英

How to use generic type constraint to enforce Add method existence

I have this code that uses the generic code:

    public static async Task<List<string>> GetDomainsAsync(IMemoryCache _cache)
    {
        return await ContextCache.CacheTryGetValueSet<List<String>>("SP_GET_DOMAINS", _cache);
    }

    public static async Task<Dictionary<String, String>> GetSettingsAsync(IMemoryCache _cache)
    {
        return await ContextCache.CacheTryGetValueSet<Dictionary<String, String>>("SP_GET_SETTINGS", _cache);
    }

And this is the generic method:

private static async Task<T> CacheTryGetValueSet<T>(String SPName, IMemoryCache _cache) where T : new()
{
          dynamic data = new T();


        ....

         while (reader.Read())
                                        {
                                            if (reader.FieldCount == 1)
                                            {
                                                data.Add(reader.GetString(0));
                                            }
                                            else if (reader.FieldCount == 2)
                                            {
                                                data.Add(reader.GetString(0), reader.GetString(1));
                                            }
                                        }
        ....

        return data;

        }

How can I make sure that T in-fact has an Add method? What generic type constraint can be added to make sure that only IEnumerable can be passed?

Whenever you have a generic method that contains if (typeof(T)) , you are probably doing it wrong. The whole point of generics is that they operate exactly the same on a variety of types. If the types are too different (eg a dictionary that needs two fields versus a list that needs one) you end up writing non-generic code in a generic method, which just confuses things.

In this case you should probably have two methods, one that accepts one type parameter (which returns an IEnumerable<TItem> , which the caller can easily convert to a List or use in LINQ statements) and one that accepts two type parameters (which returns an IEnumerable<KeyValuePair<TKey,TValue>> , which the caller can convert to a dictionary or use in LINQ).

In addition, you probably should use yield return as rows become available so you don't have to read the entire rowset before the caller can start processing data. This will smooth out your performance, and neatly avoids the issue of figuring out whether there is an Add() method-- you don't need it.

Here is an example of a way you could rewrite your method to address these issues. It's a little tricky because you have to use a nested function to use yield return in an async Task :

public async Task<IEnumerable<TItem>> CacheTryGetValueSet<TItem>(string storedProcedureName, IMemoryCache cache)
{
    IEnumerable<TItem> Enumerate(SqlDataReader source)
    {
        while (source.Read())
        {
            yield return source.GetFieldValue<TItem>(0);
        }
    }

    var reader = await OpenReaderAsync(storedProcedureName);
    if (reader.FieldCount != 1) throw new ArgumentException("That type of cache doesn't return a single column.");
    return Enumerate(reader);
}

public async Task<IEnumerable<KeyValuePair<TKey,TValue>>> CacheTryGetValueSet<TKey,TValue>(string storedProcedureName, IMemoryCache cache)
{
    IEnumerable<KeyValuePair<TKey,TValue>> Enumerate(SqlDataReader source)
    {
        while (source.Read())
        {
            yield return new KeyValuePair<TKey, TValue>
            (
                source.GetFieldValue<TKey>(0),
                source.GetFieldValue<TValue>(1)
            );
        }
    }

    var reader = await OpenReaderAsync(storedProcedureName);
    if (reader.FieldCount != 2) throw new ArgumentException("That type of cache doesn't return two columns!");
    return Enumerate(reader);
}

Now the caller can call it this way:

public static async Task<List<string>> GetDomainsAsync(IMemoryCache _cache)
{
    return await ContextCache.CacheTryGetValueSet<string>("SP_GET_DOMAINS", _cache).ToList();
}

public static async Task<Dictionary<String, String>> GetSettingsAsync(IMemoryCache _cache)
{
    return await ContextCache.CacheTryGetValueSet<String, String>("SP_GET_SETTINGS", _cache).ToDictionary();
}

您可以尝试使用ICollection<string>接口作为泛型类型约束where T : ICollection<string>, new()

There's no way to guarantee that there's a specific method on a generic at compile time, but you can enforce that the generic type implement an interface such as IList which has an Add method.

IE

public T Foo<T>() where T : IList, new()
{
  var list = new T();
  list.Add(...);
  return list;
}

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