我正在设计一个泛型列定义类,它将充当实体属性的选择器,所有这些都使得管理LOB应用程序中不同方面的网格表示变得更加容易。
不幸的是,我遇到了一道墙,试图在类中使用泛型参数,该类将包含在集合中。下面的SettingsContext类的示例实现解释了正在发生的事情:
public interface IDisplayColumn<in T>
{
string Title { get; set; }
int Order { get; set; }
Func<T, object> Selector { get; }
}
public class DisplayColumn<T>: IDisplayColumn<T>
{
public string Title { get; set; }
public int Order { get; set; }
public Func<T, object> Selector { get; set; }
}
public class ColumnSet
{
public Type TypeHandled { get; set; }
public IEnumerable<IDisplayColumn<object>> Columns { get; set; }
}
public static class ColumnSetTest
{
static ColumnSetTest()
{
// Cannot implicitly convert type 'DisplayColumn<System.Configuration.SettingsContext>' to 'IDisplayColumn<object>'.
// An explicit conversion exists (are you missing a cast?)
IDisplayColumn<object> testSingleColumn = new DisplayColumn<SettingsContext> {Title = "Test", Selector = x => x.Values };
// another test with other type used as a source which should be assignable to DisplayColumn<object>
testSingleColumn = new DisplayColumn<SettingsProvider> { Title="Another test", Selector = x => x.ApplicationName };
// Cannot implicitly convert type 'System.Collections.Generic.List<IDisplayColumn<System.Configuration.SettingsContext>>'
// to 'System.Collections.Generic.IEnumerable<IDisplayColumn<object>>'.
// An explicit conversion exists (are you missing a cast?)
var columnSets = new List<ColumnSet>
{
new ColumnSet
{
TypeHandled = typeof(SettingsContext),
Columns = new List<IDisplayColumn<SettingsContext /* or object */>>
{
new DisplayColumn<SettingsContext> {Title = "Column 1", Order = 1, Selector = x => x.IsReadOnly },
new DisplayColumn<SettingsContext> {Title = "Column 2", Order = 2, Selector = x => x.IsSynchronized },
new DisplayColumn<SettingsContext> {Title = "Column 3", Order = 3, Selector = x => x.Keys }
}
}
};
}
}如何理解协方差和反方差的目的--这是真正的预期- out参数应该用于IDisplayColumn testSingleColumn =新的DisplayColumn赋值,但是我需要使Func在参数泛化中,out将始终是一个对象。
如何实现这样的场景,它是否需要实现自定义的Func,或者dotnet已经有了一种适合这种用途的类型?
目前,我看到的唯一解决方案是使用DisplayColumn对象、object > Selector属性创建非泛型的Func<类,并将参数转换为每个赋值中的具体类型,这显然不是一个很好的解决方案。另一种选择是继承基本的非泛型DisplayColumn类,并将泛型选择器放在继承的泛型类中,但是在显示数据时使用这个表达式将需要在继承的泛型类中调用泛型方法,这是性能和代码质量标准所不能接受的。
发布于 2013-10-02 08:40:28
经过彻底的调查,我发现解决方案需要混合协方差和反方差,这是目前不支持的。最接近的解决方案(编译)实际上不允许轻松访问IDisplayColumn.Selector,因为IColumnSet.Columns中的T参数将作为对象而不是IDisplayColumn可见,因此它不是一个选项:
public interface IDisplayColumn<in T>
{
string Title { get; set; }
int Order { get; set; }
Func<T, object> Selector { get; }
}
public class DisplayColumn<T> : IDisplayColumn<T>
{
public string Title { get; set; }
public int Order { get; set; }
public Func<T, object> Selector { get; set; }
}
public interface IColumnSet<out T>
{
Type TypeHandled { get; }
IEnumerable<T> Columns { get; }
}
public class ColumnSet<T> : IColumnSet<IDisplayColumn<T>>
{
public Type TypeHandled
{
get
{
return typeof(T);
}
}
public IEnumerable<IDisplayColumn<T>> Columns { get; set; }
}最后,我翻译了Func<,>在创建时使用表达式,这是一次操作,使用选择器时转换的开销最小:
public interface IDisplayColumn
{
string Title { get; set; }
bool Visible { get; set; }
int Order { get; set; }
Func<object, object> Value { get; }
T GetValue<T>(object source);
}
public class DisplayColumn<T>: IDisplayColumn
{
public string Title { get; set; }
public bool Visible { get; set; }
public int Order { get; set; }
public Func<object, object> Value { get; set; }
public override string ToString()
{
return Title;
}
public TValue GetValue<TValue>(object source)
{
return (TValue)Convert.ChangeType(Value(source), typeof(TValue));
}
public Func<T, object> Selector
{
set
{
Value = value.ConvertObject<T>();
}
}
}
public interface IColumnSet
{
Type TypeHandled { get; }
IEnumerable<IDisplayColumn> Columns { get; }
}
public class ColumnSet<T>: IColumnSet
{
public Type TypeHandled
{
get
{
return typeof(T);
}
}
public IEnumerable<IDisplayColumn> Columns { get; set; }
}
public static Func<object, object> ConvertObject<T>(this Func<T, object> func)
{
Contract.Requires(func != null);
var param = Expression.Parameter(typeof(object));
var convertedParam = new Expression[] { Expression.Convert(param, typeof(T)) };
Expression call;
call = Expression.Convert(
func.Target == null
? Expression.Call(func.Method, convertedParam)
: Expression.Call(Expression.Constant(func.Target), func.Method, convertedParam)
, typeof(object));
var delegateType = typeof(Func<,>).MakeGenericType(typeof(object), typeof(object));
return (Func<object, object>)Expression.Lambda(delegateType, call, param).Compile();
}以及使用的例子:
private class TestObject1
{
public int Id { get; set; }
public string Name { get; set; }
}
IDisplayColumn objectColumn = new DisplayColumn<TestObject1> { Title = "Column 1", Selector = (x) => x.Name };
var columnSets = new List<IColumnSet>
{
new ColumnSet<TestObject1>
{
Columns = new List<IDisplayColumn>
{
new DisplayColumn<TestObject1> { Title = "Column 1", Order = 3, Selector = x => x.Id },
new DisplayColumn<TestObject1> { Title = "Column 2", Order = 2, Selector = x => x.Name },
new DisplayColumn<TestObject1> { Title = "Column 3", Order = 1, Selector = x => x.Id.ToString(CultureInfo.InvariantCulture) + x.Name.ValueOrEmpty() },
}
}
};所以我会把这个问题归功于我自己,但是如果有人可以用泛型和方差提出一个更好的解决方案,请随意发布,因为我会很乐意改变这个解决方案。
发布于 2013-09-30 09:27:20
如果您的ColumnSet也是通用的,那么您可以为它返回的可枚举列指定使用的类型。下面的代码将编译,我认为实现您所追求的目标。
public interface IDisplayColumn<in T>
{
string Title { get; set; }
int Order { get; set; }
Func<T, object> Selector { get; }
}
public class DisplayColumn<T>: IDisplayColumn<T>
{
public string Title { get; set; }
public int Order { get; set; }
public Func<T, object> Selector { get; set; }
}
public class ColumnSet<T>
{
public Type TypeHandled { get; set; }
public IEnumerable<IDisplayColumn<T>> Columns { get; set; }
}
public static class ColumnSetTest
{
static ColumnSetTest()
{
IDisplayColumn<SettingsContext> testSingleColumn = new DisplayColumn<SettingsContext> { Title = "Test", Selector = x => x.Values };
var columnSets = new List<ColumnSet<SettingsContext>>
{
new ColumnSet<SettingsContext>
{
TypeHandled = typeof(SettingsContext),
Columns = new List<IDisplayColumn<SettingsContext>>
{
new DisplayColumn<SettingsContext> {Title = "Column 1", Order = 1, Selector = x => x.IsReadOnly },
new DisplayColumn<SettingsContext> {Title = "Column 2", Order = 2, Selector = x => x.IsSynchronized },
new DisplayColumn<SettingsContext> {Title = "Column 3", Order = 3, Selector = x => x.Keys }
}
}
};}}
https://stackoverflow.com/questions/19090297
复制相似问题