如何根据约束比较泛型类型

时间:2017-10-14 19:50:17

标签: c# generics reflection type-inference

假设我有两个类,Foo和Bar。

public class Foo<TFirst, TSecond, TThird, T> 
       where TFirst : IReadOnlyList<T>
       where TThird : IEnumerable<T>
{
}

public class Bar<TFirst, TSecond, TThird, T> 
       where TFirst : IReadOnlyList<T>
{
}

现在我想比较他们的泛型类型。我正在使用等式比较器来操作类型数组。比如Intersect,Subtract等。

我不想比较FooBar,但我想比较他们的通用参数。

例如,如果两个类型参数具有相同的约束,则应将它们视为相等。如果他们没有约束,他们也应该被认为是平等的。

在上面的示例中,Foo的TFirst应该被视为等于Bar的TFirst。以及TSecond,因为他们没有约束。 TThrid不相等,因为它们没有相同的约束。

所以现在我有Foo和Bar类型。我想分析他们的类型参数并将它们相互比较。

var fooType = typeof(Foo<,,,>);
var barType = typeof(Bar<,,,>);

var fooArgs = fooType.GetGenericArguments();
var barArgs = barType.GetGenericArguments();

var commonArgs = fooArgs.Intersect(barArgs, new GenericArgumentEqualityComparer()).ToArray();

var unknownBarArgs = barArgs.Except(commonArgs, new GenericArgumentEqualityComparer()).ToArray();

以下Equality comparer始终返回false,无论我使用IsAssignableFrom还是==。这样做的正确方法是什么?

public class GenericArgumentEqualityComparer : IEqualityComparer<Type>
{
    public bool Equals(Type x, Type y)
    {
        if (x == null || y == null) return false;
        var xcons = x.GetGenericParameterConstraints();
        var ycons = y.GetGenericParameterConstraints();

        if(xcons.Length != ycons.Length) return false;

        foreach (var cons in xcons)
        {
            if (ycons.All(cons2 => !cons.IsAssignableFrom(cons2)))
                return false;
        }
        return true;
    }

    public int GetHashCode(Type obj)
    {
        // code runs on T4 for code generation. performance doesn't matter.
        return 0;
    }
}

1 个答案:

答案 0 :(得分:0)

意外行为来自

cons.IsAssignableFrom

即使分层可能,也不能尝试从派生到基础分配开放泛型类型 以此为例

var ien = typeof(IEnumerable<string>);
            var iread = typeof(IReadOnlyList<string>);
            //isAssignable will be true 
            var isAssignable = ien.IsAssignableFrom(iread);
            //here because IsAssignableFrom work from BaseType.IsAssignableFrom(DerviedType)       
            var isAssignableIEn = iread.IsAssignableFrom(ien);

和这一个

var ien = typeof(IEnumerable<>);
            var iread = typeof(IReadOnlyList<>);
            var isAssignable = ien.IsAssignableFrom(iread);
            //here because IsAssignableFrom work from BaseType.IsAssignableFrom(DerviedType)       
            var isAssignableIEn = iread.IsAssignableFrom(ien);

两个赋值检查都是false这是预期的行为,因为默认情况下无法实例化开放泛型类型,因此无法分配

要创建开放泛型类型的实例,您应该使用Type.MakeGenericType

要解决您的问题,这可能会对您有所帮助

public class GenericArgumentEqualityComparer : IEqualityComparer<Type>
    {
        public bool Equals(Type x, Type y)
        {

            var xInterfacesTypes = x.GetInterfaces();
            var yInterfacesTypes = y.GetInterfaces();
            if (!xInterfacesTypes.Any()&&!yInterfacesTypes.Any() )
            {
                return true; 
            }
            if ((!xInterfacesTypes.Any() && yInterfacesTypes.Any()) || xInterfacesTypes.Any() && !yInterfacesTypes.Any())
            {
                return false; 
            }          
            foreach (var xInterfacesType in xInterfacesTypes)
            {
                var iType = xInterfacesType.IsGenericType ? xInterfacesType.GetGenericTypeDefinition() :xInterfacesType;
                var yType = yInterfacesTypes.Any(yI => yI.IsGenericType && yI.GetGenericTypeDefinition() == iType||yI.GetType()==xInterfacesType.GetType());
                if (!yType)
                {
                    return false;
                }

            }
            return true; 
        }

        public int GetHashCode(Type obj)
        {
            return obj.Name.GetHashCode(); 
        }
    }