我希望通过将类型作为参数传递给targetUnitOfWork.Query
来缩短此代码。
SomeListItem
和SomeList
有两种类型。根据实际类型,我必须调用Query<SomeListItem>
或Query<SomeList>
,如下所示。
Type typeName = targetClassInfo.ClassType;
if (typeName.Equals(typeof(SomeListItem)))
{
target = targetUnitOfWork
.Query<SomeListITem>()
.Where(i => i.Name.Equals(otherObj.Name)).Where(j => j.SortKey == otherObj.SortKey);
}
else if (typeName.Equals(typeof(SomeList)))
{
target = targetUnitOfWork
.Query<SomeList>()
.Where(i => i.Name.Equals(otherObj.Name)).Where(j => j.SortKey == otherObj.SortKey);
}
else
{
target = targetClassInfo.CreateNewObject(targetUnitOfWork);
}
我该如何解决这个问题?
答案 0 :(得分:1)
为什么不使用这样的通用方法:
private void SomeMethod<T>()
{
target = targetUnitOfWork
.Query<T>()
.Where(i => i.Name.Equals(otherObj.Name)).Where(j => j.SortKey == otherObj.SortKey);
}
然后您可以致电SomeMethod<SomeList>()
或SomeMethod<SomeListItem>()
答案 1 :(得分:1)
这就是你想要做的,对吧?
class Program
{
static void Main(string[] args)
{
List<object> listOfObjects = new List<object>() { new Item(), new Dog(), new Cat(), new Human() };
Dog martin = GetFirstOrDefault<Dog>(listOfObjects);
}
static T GetFirstOrDefault<T>(List<object> listOfObjects)
{
return (T)listOfObjects.Where(x => x.GetType() == typeof(T)).FirstOrDefault();
}
}
class Item
{
public string Name { get; set; }
public string Color { get; set; }
}
class Dog
{
public string Name { get; set; }
public int Age { get; set; }
}
class Cat
{
public string Name { get; set; }
public int Age { get; set; }
}
class Human
{
public string Name { get; set; }
public DateTime Birth { get; set; }
}
答案 2 :(得分:1)
从现在开始,事情只会变得复杂(凌乱?)。
好的,所以前两个查询是一样的。所以,你可能会选择通用方法。这种东西:
public IEnumerable<T> GetListTarget<T>(bool applyWhere) // You will need to add an constraint here that is applicable to both classes. Only then compiler will be able to understand the properties you are using in the where method
{
if (applyWhere)
{
return targetUnitOfWork
.Query<T>()
.Where(i => i.Name.Equals(otherObj.Name)).Where(j => j.SortKey == otherObj.SortKey);
}
else
{
return targetClassInfo.CreateNewObject(targetUnitOfWork);
}
}
答案 3 :(得分:1)
我们不会在此答案中涵盖代码作者所做的设计决策。值得一提的是,这种异构泛型应该留给模式匹配机制而不是多态机制。
无论哪种方式,在很多情况下,您希望动态放置泛型类型并在链中调用方法。它主要在服务库和框架的项目中完成,供以后使用,其中参数从用户输入继承,或者最近由开发人员作为扩展来到项目中。
幸运的是,.NET Framework(以及Core,以下代码是.NET Framework)提供了一个丰富的Reflection
库,您可以在其中执行元编程模型。
Reflection
库提供了一种内省程序的方法,有利于静态典型化,有利于动态一代,例如按名称查找方法,因为它来自用户输入。这不是它的唯一目的,但我们将以这种方式使用它。
在我们的场景中,我们需要使用来自用户输入的任意Query<T>
来调用<T>
方法。因此,让我们定义一个将提供此功能的函数。我们将其称为Test
:
static void Test(Type type, TestGenerics testGenerics, String otherObjectName)
它会收到System.Type
,我们案例中的对象TestGenerics
和String
来测试问题中的name
属性。
我们的TestGenerics
对象是一个模仿问题语义的假类:
class TestGenerics
{
public IEnumerable<T> Query<T>() where T : new()
{
return Enumerable.Repeat(new T(), 10);
}
}
首先,我们需要按名称查找Query
方法。由于它是以这种方式命名的唯一方法(无重载),我们可以安全地使用FirstOrDefault
:
Object enumerable = testGenerics.GetType().GetMethods().FirstOrDefault(m => m.Name == "Query")
但我们不能直接调用它,因为它不仅接受参数,还接受通用参数。我们可以通过提供Type
到MakeGenericMethod(Type)
反射的方法来提供它们:
.MakeGenericMethod(type)
然后我们准备Invoke
没有参数(因为它不接受任何参数),但我们需要指定它将被调用的对象(在我们的例子中testGenerics
):< / p>
.Invoke(testGenerics, null);
到目前为止酷,有龙来到这里,因为我们现在需要建立i => i.name == otherObjectName
lambda。来自Where
扩展名的IEnumerable<T>
方法(事实上,它是System.Linq.Enumerable中的静态方法)会收到Func<T, R>
而不是Predicate<T>
,因此我们需要构建一个:
Type predicateType = typeof(Func<,>).MakeGenericType(type, typeof(bool));
这会构建Func<,>
,例如具有两个通用参数的Func
类型。第一个是传递的类型,第二个是布尔值,用于按函数模拟谓词。
现在我们需要通过创建给定类型的参数来构建lambdas左侧:
ParameterExpression predParam = Expression.Parameter(type, "i");
从中获取字段name
:
Expression left = Expression.Field(predParam, type.GetField("name"));
表达式的右侧是我们将其与之比较的名称:
Expression right = Expression.Constant(otherObjectName, typeof(string));
构建整个lambda是下一步。从谓词类型(Func<T, R>
,等式表达式和谓词参数“i”):
LambdaExpression lambda = Expression.Lambda(predicateType, Expression.Equal(left, right), predParam);
现在我们需要找到Where
方法。它位于包含所有扩展方法的类中,而不在IEnumerable
接口中:
IEnumerable<MethodInfo> methodsEnumerable = typeof(System.Linq.Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public);
MethodInfo where = methodsEnumerable.Where(m => m.GetParameters().Length == 2).FirstOrDefault(m => m.Name == "Where");
但这是一个通用方法,从输入中接收类型,所以我们也需要这样做:
MethodInfo genericWhere = where.MakeGenericMethod(type);
由于它是静态方法,因此必须将对象作为参数传递(对于扩展方法的语义)。对象数组中的第一个参数是扩展接口(IEnumerable
,例如返回类型Query
),第二个参数是上面的lambda - 编译:
Object response = genericWhere.Invoke(enumerable, new[] {enumerable, lambda.Compile()});
在这里,我们将停止这个例子。您需要针对您的情况调整它并添加其他方法调用。它也非常冗长和丑陋,但适用于包含name
字段的任何类型的对象。在更大的场景中,如果您不将某个字段硬编码到某个字段,它将适用于各种输入。框架如何与我们的代码一起使用。
您可以在下面找到完整示例:
class TypeOne
{
public string name;
}
class TypeTwo
{
public string name;
}
internal class Program
{
public static void Main(string[] args)
{
Test(typeof(TypeOne), new TestGenerics(), "John");
Test(typeof(TypeTwo), new TestGenerics(), "Smith");
}
static void Test(Type type, TestGenerics testGenerics, String otherObjectName)
{
Object enumerable = testGenerics.GetType().GetMethods().FirstOrDefault(m => m.Name == "Query")
.MakeGenericMethod(type)
.Invoke(testGenerics, null);
Type predicateType = typeof(Func<,>).MakeGenericType(type, typeof(bool));
ParameterExpression predParam = Expression.Parameter(type, "i");
Expression left = Expression.Field(predParam, type.GetField("name"));
Expression right = Expression.Constant(otherObjectName, typeof(string));
LambdaExpression lambda = Expression.Lambda(predicateType, Expression.Equal(left, right), predParam);
IEnumerable<MethodInfo> methodsEnumerable = typeof(System.Linq.Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public);
MethodInfo where = methodsEnumerable.Where(m => m.GetParameters().Length == 2).FirstOrDefault(m => m.Name == "Where");
MethodInfo genericWhere = where.MakeGenericMethod(type);
Object response = genericWhere.Invoke(enumerable, new[] {enumerable, lambda.Compile()});
Console.WriteLine(response);
}
}
class TestGenerics
{
public IEnumerable<T> Query<T>() where T : new()
{
return Enumerable.Repeat(new T(), 10);
}
}