无法使用涉及多重继承的对象访问基 class 中的正确属性
Can't access the correct properties in base class with object involving multiple inheritance
有些事情我想不通。我有以下代码:
public interface IProject
{
}
public class ModuleProject : IProject
{
}
public class AllProject : IProject
{
public ModuleProject ModuleData => new ModuleProject();
}
public interface IProjectInstance
{
IProject Data { get; }
}
public interface IModuleProjectInstance : IProjectInstance
{
new ModuleProject Data { get; }
}
public interface IAllProjectInstance : IModuleProjectInstance
{
}
public class AllProjectInstance : IAllProjectInstance
{
IProject IProjectInstance.Data => Data;
public AllProject Data => new AllProject();
ModuleProject IModuleProjectInstance.Data => Data.ModuleData;
}
public abstract class Worker<T,VM>
where T:class, IProject
where VM:class, IProjectInstance
{
private readonly T _data;
protected Worker(VM viewModel)
{
_data = (T)viewModel.Data; //fails to cast
}
}
public class ModuleWorker : Worker<ModuleProject, IModuleProjectInstance>
{
public ModuleWorker(IModuleProjectInstance viewModel) : base(viewModel)
{
}
}
当我运行下面的代码时:
var projInstance = new AllProjectInstance();
var moduleWorker = new ModuleWorker(projInstance);
由于 InvalidCastException
,程序在 _data = (T)viewModel.Data
崩溃。
System.InvalidCastException HResult=0x80004002 Message=Unable to
cast object of type 'ProjectInstance.AllProject' to type
'ProjectInstance.ModuleProject'. Source=ProjectInstance
StackTrace: at ProjectInstance.Worker`2..ctor(VM viewModel) in
D:.net Samples project\August 2021\ProjectInstance\Worker.cs:line 15
at ProjectInstance.ModuleWorker..ctor(IModuleProjectInstance
viewModel) in D:.net Samples project\August
2021\ProjectInstance\Worker.cs:line 24 at
ProjectInstance.Program.Main(String[] args) in D:.net Samples
project\August 2021\ProjectInstance\Program.cs:line 16
看来 viewModel
是 AllProjectInstance
类型,AllProjectInstance.Data
是 AllProject
类型,但 T
是 ModuleProject
,因此例外。很公平。
但是假设我稍微修改一下代码:
public abstract class Worker<T,VM>
where T:class, IProject
where VM:class, IProjectInstance
{
private readonly T _data;
protected Worker(T data) //this works!
{
_data = data;
}
}
public class ModuleWorker : Worker<ModuleProject, IModuleProjectInstance>
{
public ModuleWorker(IModuleProjectInstance viewModel) : base(viewModel.Data)
{
}
}
那就成功了!因为在 ModuleWorker
构造函数中, viewModel
被推断为 IModuleProjectInstance
的类型,因此它是 AllProjectInstance
内部的 ModuleProject IModuleProjectInstance.Data
属性被调用,因此也不例外。
我的问题:
- C# 规范中是否记录了此行为?
- 这样设计的合理性是什么?
- 有没有办法将其转换为基础 class (
Worker<T,VM>
) 以便推断出正确的 属性?
在Worker
的构造函数中,viewModel.Data
是一个需要执行的member access, and to evaluate this member access, a member lookup。这是 Data
类型 VM
(viewModel
的类型)的成员查找,具有 0 个类型参数。让我们看看如何根据规范进行成员查找。
(T
指的是我们要查找的成员的类型,所以这里=VM
。N
是我们要查找的成员的名称,所以 = Data
):
First, a set of accessible members named N
is determined:
- If
T
is a type parameter, then the set is the union of the sets of accessible members named N
in each of the types specified as a primary constraint or secondary constraint for T
, along with the set of accessible members named N
in object
.
- Otherwise, the set consists of all accessible members named
N
in T
, including inherited members and the accessible members named N
in object
. [...]
VM
是类型参数,VM
的约束是class
和IProjectInstance
,所以第一点适用。集合只有属性IProject Data { get; }
.
其他步骤仅尝试从集合中删除元素,其中 none 个适用于本例,因此我不会重复这些步骤。最后,IProjectInstance
声明的Data
属性就是查找的结果
现在,AllProjectInstance
中的哪个实现对应于IProjectInstance.Data
的实现?当然是IProjectInstance
的显式接口实现
// This returns an AllProject object, causing the cast to fail
IProject IProjectInstance.Data => Data;
另一方面,如果在 ModuleWorker
构造函数中执行 viewModel.Data
,则 viewModel
的类型为 IModuleProjectInstance
。如果你应用相同的成员查找算法,你会看到这次它查找了IModuleProjectInstance
中声明的Data
属性,而AllProjectInstance
中对应的实现是:
ModuleProject IModuleProjectInstance.Data => Data.ModuleData;
我不确定您为什么认为这出乎意料。 IMO 函数成员自然不知道调用者调用它们的表达式。如果您希望 viewModel.Data
解析为 IModuleProjectInstance.Data
,则需要 Worker
构造函数知道它已使用编译时类型为 IModuleProjectInstance
的表达式调用。也就是说,这在 C# 中并非不可能 - 您可以要求调用者传入一个 Expression<Func<VM>>
并操作表达式树以找出其编译时类型,并获得您想要的 Data
反射,但这太过分了。
与其进行转换,不如将 IProjectInstance
设为通用:
public interface IProjectInstance<T> where T: IProject
{
T Data { get; }
}
public interface IModuleProjectInstance : IProjectInstance<ModuleProject>
{
}
public interface IAllProjectInstance : IModuleProjectInstance
{
}
public class AllProjectInstance : IAllProjectInstance, IProjectInstance<AllProject>
{
public AllProject Data => new AllProject();
ModuleProject IProjectInstance<ModuleProject>.Data => Data.ModuleData;
}
public abstract class Worker<T,VM>
where T:class, IProject
where VM:class, IProjectInstance<T>
{
private readonly T _data;
protected Worker(VM viewModel)
{
_data = viewModel.Data;
}
}
如果实在不行IProjectInstance
泛型,也可以尝试用反射获取第二个类型参数的类型,找到Data
属性,得到它的价值。
protected Worker(VM viewModel)
{
var dataProperty = GetType().BaseType.GetGenericArguments()[1].GetProperty("Data");
if (dataProperty != null) {
_data = (T)dataProperty.GetValue(viewModel);
} else { // fall back to your original approach
_data = (T)viewModel.Data;
}
}
这里对Worker
的子类做了一些假设,比如:
- 其直接超类是
Worker
VM
确实有一个可访问的 Data
属性 returns 可以转换为 T
的东西。现在,如果 IModuleProjectInstance.Data
被删除,ModuleWorker
仍然可以编译。
有些事情我想不通。我有以下代码:
public interface IProject
{
}
public class ModuleProject : IProject
{
}
public class AllProject : IProject
{
public ModuleProject ModuleData => new ModuleProject();
}
public interface IProjectInstance
{
IProject Data { get; }
}
public interface IModuleProjectInstance : IProjectInstance
{
new ModuleProject Data { get; }
}
public interface IAllProjectInstance : IModuleProjectInstance
{
}
public class AllProjectInstance : IAllProjectInstance
{
IProject IProjectInstance.Data => Data;
public AllProject Data => new AllProject();
ModuleProject IModuleProjectInstance.Data => Data.ModuleData;
}
public abstract class Worker<T,VM>
where T:class, IProject
where VM:class, IProjectInstance
{
private readonly T _data;
protected Worker(VM viewModel)
{
_data = (T)viewModel.Data; //fails to cast
}
}
public class ModuleWorker : Worker<ModuleProject, IModuleProjectInstance>
{
public ModuleWorker(IModuleProjectInstance viewModel) : base(viewModel)
{
}
}
当我运行下面的代码时:
var projInstance = new AllProjectInstance();
var moduleWorker = new ModuleWorker(projInstance);
由于 InvalidCastException
,程序在 _data = (T)viewModel.Data
崩溃。
System.InvalidCastException HResult=0x80004002 Message=Unable to cast object of type 'ProjectInstance.AllProject' to type 'ProjectInstance.ModuleProject'. Source=ProjectInstance
StackTrace: at ProjectInstance.Worker`2..ctor(VM viewModel) in D:.net Samples project\August 2021\ProjectInstance\Worker.cs:line 15 at ProjectInstance.ModuleWorker..ctor(IModuleProjectInstance viewModel) in D:.net Samples project\August 2021\ProjectInstance\Worker.cs:line 24 at ProjectInstance.Program.Main(String[] args) in D:.net Samples project\August 2021\ProjectInstance\Program.cs:line 16
看来 viewModel
是 AllProjectInstance
类型,AllProjectInstance.Data
是 AllProject
类型,但 T
是 ModuleProject
,因此例外。很公平。
但是假设我稍微修改一下代码:
public abstract class Worker<T,VM>
where T:class, IProject
where VM:class, IProjectInstance
{
private readonly T _data;
protected Worker(T data) //this works!
{
_data = data;
}
}
public class ModuleWorker : Worker<ModuleProject, IModuleProjectInstance>
{
public ModuleWorker(IModuleProjectInstance viewModel) : base(viewModel.Data)
{
}
}
那就成功了!因为在 ModuleWorker
构造函数中, viewModel
被推断为 IModuleProjectInstance
的类型,因此它是 AllProjectInstance
内部的 ModuleProject IModuleProjectInstance.Data
属性被调用,因此也不例外。
我的问题:
- C# 规范中是否记录了此行为?
- 这样设计的合理性是什么?
- 有没有办法将其转换为基础 class (
Worker<T,VM>
) 以便推断出正确的 属性?
在Worker
的构造函数中,viewModel.Data
是一个需要执行的member access, and to evaluate this member access, a member lookup。这是 Data
类型 VM
(viewModel
的类型)的成员查找,具有 0 个类型参数。让我们看看如何根据规范进行成员查找。
(T
指的是我们要查找的成员的类型,所以这里=VM
。N
是我们要查找的成员的名称,所以 = Data
):
First, a set of accessible members named
N
is determined:
- If
T
is a type parameter, then the set is the union of the sets of accessible members namedN
in each of the types specified as a primary constraint or secondary constraint forT
, along with the set of accessible members namedN
inobject
.- Otherwise, the set consists of all accessible members named
N
inT
, including inherited members and the accessible members namedN
inobject
. [...]
VM
是类型参数,VM
的约束是class
和IProjectInstance
,所以第一点适用。集合只有属性IProject Data { get; }
.
其他步骤仅尝试从集合中删除元素,其中 none 个适用于本例,因此我不会重复这些步骤。最后,IProjectInstance
声明的Data
属性就是查找的结果
现在,AllProjectInstance
中的哪个实现对应于IProjectInstance.Data
的实现?当然是IProjectInstance
// This returns an AllProject object, causing the cast to fail
IProject IProjectInstance.Data => Data;
另一方面,如果在 ModuleWorker
构造函数中执行 viewModel.Data
,则 viewModel
的类型为 IModuleProjectInstance
。如果你应用相同的成员查找算法,你会看到这次它查找了IModuleProjectInstance
中声明的Data
属性,而AllProjectInstance
中对应的实现是:
ModuleProject IModuleProjectInstance.Data => Data.ModuleData;
我不确定您为什么认为这出乎意料。 IMO 函数成员自然不知道调用者调用它们的表达式。如果您希望 viewModel.Data
解析为 IModuleProjectInstance.Data
,则需要 Worker
构造函数知道它已使用编译时类型为 IModuleProjectInstance
的表达式调用。也就是说,这在 C# 中并非不可能 - 您可以要求调用者传入一个 Expression<Func<VM>>
并操作表达式树以找出其编译时类型,并获得您想要的 Data
反射,但这太过分了。
与其进行转换,不如将 IProjectInstance
设为通用:
public interface IProjectInstance<T> where T: IProject
{
T Data { get; }
}
public interface IModuleProjectInstance : IProjectInstance<ModuleProject>
{
}
public interface IAllProjectInstance : IModuleProjectInstance
{
}
public class AllProjectInstance : IAllProjectInstance, IProjectInstance<AllProject>
{
public AllProject Data => new AllProject();
ModuleProject IProjectInstance<ModuleProject>.Data => Data.ModuleData;
}
public abstract class Worker<T,VM>
where T:class, IProject
where VM:class, IProjectInstance<T>
{
private readonly T _data;
protected Worker(VM viewModel)
{
_data = viewModel.Data;
}
}
如果实在不行IProjectInstance
泛型,也可以尝试用反射获取第二个类型参数的类型,找到Data
属性,得到它的价值。
protected Worker(VM viewModel)
{
var dataProperty = GetType().BaseType.GetGenericArguments()[1].GetProperty("Data");
if (dataProperty != null) {
_data = (T)dataProperty.GetValue(viewModel);
} else { // fall back to your original approach
_data = (T)viewModel.Data;
}
}
这里对Worker
的子类做了一些假设,比如:
- 其直接超类是
Worker
VM
确实有一个可访问的Data
属性 returns 可以转换为T
的东西。现在,如果IModuleProjectInstance.Data
被删除,ModuleWorker
仍然可以编译。