简体   繁体   English

如何使用泛型类型约束来强制 Add 方法存在

[英]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?我怎样才能确保T实际上有一个Add方法? What generic type constraint can be added to make sure that only IEnumerable can be passed?可以添加什么泛型类型约束以确保只能传递 IEnumerable?

Whenever you have a generic method that contains if (typeof(T)) , you are probably doing it wrong.每当你有一个包含if (typeof(T))的泛型方法时,你可能做错了。 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).在这种情况下,您可能应该有两种方法,一种接受一个类型参数(返回IEnumerable<TItem> ,调用者可以轻松地将其转换为 List 或在 LINQ 语句中使用),另一种接受两个类型参数(返回一个IEnumerable<KeyValuePair<TKey,TValue>> ,调用者可以将其转换为字典或在 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.此外,您可能应该在行变得可用时使用yield return ,这样您就不必在调用者开始处理数据之前读取整个行集。 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.这将平滑您的性能,并巧妙地避免确定是否存在Add() ——您不需要它。

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 :这有点棘手,因为您必须使用嵌套函数在async Task使用yield return

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.没有办法保证在编译时泛型上有特定的方法,但您可以强制泛型类型实现一个接口,例如具有 Add 方法的 IList。

IE IE

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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