基于 Roslyn 的模拟多重继承用法模式 (第二部分)






4.93/5 (5投票s)
继续介绍 NP.WrapperGenerator.vsix Visual Studio 扩展的用法。展示更复杂的多重继承场景,包括菱形多重继承。
- 下载 VSIX.zip - 5.5 MB
- 下载 SelectableAndDescribableItemsSample 示例 - 74.8 KB
- 下载 DiamondInheritanceSample 示例 - 75.3 KB
- 下载 UniqueItemSelectionSample 示例 - 75.2 KB
- 下载 SelectedItemDisplaySample 示例 - 72.9 KB
重要提示
朋友们,我很希望能收到您关于本文档的评论,无论您喜欢还是不喜欢,我都非常感激。
引言
Visual Studio 2015 预览版附带了基于 Roslyn 的编译器,并且能够使用 Roslyn 创建 VS 扩展。这开启了以前从未存在过的无数机会,而这就是我尝试利用它的尝试。Roslyn 允许在不加载 .NET 代码的情况下对其进行分析——在此之前,这种能力仅存在于专有解决方案中,例如 ReSharper。
在《使用基于 Roslyn 的 VS 扩展在 C# 中实现适配器模式和模拟多重继承》中,我介绍了一种使用基于 Roslyn 的单文件生成器在 C# 中模拟多重继承的方法。模拟多重继承的方法基于《C# 的模拟多重继承模式》文章中描述的方法,不同之处在于“子类”包装器是使用该文章中介绍的 Visual Studio 扩展自动生成的。
本文档与《基于 Roslyn 的模拟多重继承用法模式 (第一部分)》一起,展示了 NP.WrapperGenerator.vsix VS 扩展的用法示例。其主要目的是展示如何利用此扩展实现更大的代码重用和关注点分离。我还试图回应读者们的担忧,表明生成的结构确实非常类似于多重继承,并且借助接口,我们甚至可以实现多态多重继承。
与本文档的第一部分(基于 Roslyn 的模拟多重继承用法模式 (第一部分))不同,第二部分讨论了更有趣和更复杂的场景。
这是第一部分文章的续篇,因此建议您先阅读第一部分。
如果您想了解更多关于 NP.WrapperGenerator.vsix 扩展的构建方式,请参阅《C# 的模拟多重继承模式》文章。
在文章的最后,我们将展示如何处理菱形多重继承——即两个超类继承自同一个类。
要运行示例和安装扩展,您需要安装 VS 2015 预览版。
安装 Visual Studio 扩展 NP.WrapperGenerator.vsix
为了能够通过下面的示例进行操作,您需要安装包含在 VSIS.zip 文件中的 NP.WrapperGenerator.vsix 扩展。要安装它,只需解压文件夹并双击扩展文件即可。之后,您需要重新启动您的 VS 2015 实例,以便它们能够使用新扩展。
如果您玩过上一篇文章中的示例,您可能已经安装了此扩展的旧版本。在这种情况下,您需要先卸载它,然后再安装新版本。为此,请在您的 VS 2015 的任何实例中转到“工具”->“扩展和更新”菜单项。找到 WrapperGenerator 扩展(通常在底部),单击它,然后按“卸载”按钮。
唯一项目选择示例
此示例位于 UniqueItemSelectionSample 解决方案下。此示例的想法来自我之前的一篇 WPF 文章——《基于 View-ViewModel 的 WPF 和 XAML 实现模式》。
该示例演示了如何构建一个非可视化的非可视项集合,该集合一次只允许选择一项——当选择一项时,之前选择的项将被取消选择。
对这些项唯一的约束是它们应该实现 ISelectable
接口
public interface ISelectable { bool IsItemSelected { get; set; } event ActionItemSelectedChangedEvent; }
并且该接口的实现应该在 IsItemSelected
属性更改时触发 ItemSelectedChangedEvent
。
在 WPF 文章中,所有可选项都派生自 Selectable
类。在这里,我们展示了如何使用包装器生成来“派生”我们的类,使其成为 Selectable
。
首先,请尝试运行项目。您将看到以下内容
有两行不同的对象,顶行是人,底行是汽车。
拥有两行不同的原因是显示完全不同的类——Person
和 Car
——可以使用我们的包装器“继承”来使它们成为 ISelectable
,而无需真正的 C# 继承。
当您单击一个按钮时,它会被选中,并且同一行中先前选中的按钮会被取消选中。
现在,让我们看一下代码。这是 Selectable
类的实现
public class Selectable : ISelectable, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropChanged(string propName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propName)); } public event ActionItemSelectedChangedEvent; bool _isItemSelected = false; public bool IsItemSelected { get { return _isItemSelected; } set { if (_isItemSelected == value) return; _isItemSelected = value; if (ItemSelectedChangedEvent != null) ItemSelectedChangedEvent(this); OnPropChanged("IsItemSelected"); } } public void ToggleIsSelected() { IsItemSelected = !IsItemSelected; } }
ItenSelectedChangedEvent
被设置为在 IsItemSelected
属性更改时触发。可选项对象本身作为唯一参数传递给它。此外,为了让 WPF 绑定注意到 IsItemSelected
属性的变化,我们使用 OnPropChanged(...)
方法触发 PropertyChanged
事件。
Person
类只有一个简单的属性——Name
public class Person { public string Name { get; set; } }
Car
类有两个简单的属性——Make
和 Model
public class Car { public string Make { get; set; } public string Model { get; set; } }
SelectablePerson
类(在包装器的意义上)派生自 Selectable
和 Person
类
[OneWayEventWraps(typeof(Selectable), "PropertyChanged", "sender")] [Wraps(typeof(Selectable), WrappedItemsKind.Event, "ItemSelectedChangedEvent")] [Wraps(typeof(Selectable), WrappedItemsKind.Property, "IsItemSelected")] [Wraps(typeof(Selectable), WrappedItemsKind.Method, "ToggleIsSelected")] [Wraps(typeof(Person), WrappedItemsKind.Property, "Name")] public partial class SelectablePerson : ISelectable, INotifyPropertyChanged { public SelectablePerson(string name) { this.TheSelectable = new Selectable(); this.ThePerson = new Person { Name = name }; } }
正如在《基于 Roslyn 的模拟多重继承用法模式 (第一部分)》中所解释的,这些属性指定了要包装的类型和成员——例如
[Wraps(typeof(Selectable), WrappedItemsKind.Event, "ItemSelectedChangedEvent")]
包装了 Selectable
类的 ItemSelectedChangedEvent
。
实际上,SelectablePerson.wrapper.cs
部分类定义是生成的,其中包含包装器。(为了实现这一点——您需要将 SelectablePerson
类的“自定义工具”属性设置为字符串“WrapperFileCodeGenerator”——正如本文档的第一部分所述)。
在 Wraps
属性中提到 Selectable
和 Person
类型,导致在 SelectablePerson.wrapper.cs 文件中生成了相应的 Selectable
类型的属性 TheSelectable
和 Person
类型的属性 ThePerson
。
public Selectable TheSelectable { get { return _selectable; } set { if ((_selectable != null)) { _selectable.PropertyChanged -= _selectable_PropertyChanged; } _selectable = value; if ((_selectable != null)) { _selectable.PropertyChanged += _selectable_PropertyChanged; } } } public Person ThePerson { get { return _person; } set { _person = value; } }
TheSelectable
属性的 setter 中设置 PropertyChanged
事件处理程序的代码将在下面解释。
由 TheSelectable
和 ThePerson
属性指定的 Selectable
和 Person
对象现在扮演着基类对象的作用。所有包装器都围绕着这些对象的成员构建。例如,这是 ItemSelectedChangedEvent
包装器的定义
public event ActionItemSelectedChangedEvent { add { _selectable.ItemSelectedChangedEvent += value; } remove { _selectable.ItemSelectedChangedEvent -= value; } }
这是我们定义 ToggleIsSelected
方法包装器的方式
public void ToggleIsSelected() { _selectable.ToggleIsSelected(); }
这是我们包装 Person
类的 Name
属性的方式
public String Name { get { return _person.Name; } set { _person.Name = value; } }
现在,让我解释一下在 TheSelectable
属性定义中添加 PropertyChanged
事件处理程序中的代码。这段代码来自 OneWayEventWraps
属性。
[OneWayEventWraps(typeof(Selectable), "PropertyChanged", "sender")]
有关使用 this
引用替换的单向事件包装的详细说明,请参阅《基于 Roslyn 的模拟多重继承用法模式 (第一部分)》。
此属性会产生一种特殊的事件包装——我们不是定义事件的 add
和 remove
部分,而是向“基”对象的事件添加一个事件处理程序。
if ((_selectable != null)) { _selectable.PropertyChanged += _selectable_PropertyChanged; }
在事件处理程序中,我们调用“派生”对象上相同的事件,只是将 this
引用传递给事件的一个参数,而不是其他参数。
private void _selectable_PropertyChanged(Object sender, PropertyChangedEventArgs e) { if ((PropertyChanged != null)) { PropertyChanged(this, e); } }
由 OneWayEventWraps
属性的第三个参数定义的要替换为 this
引用的参数——在本例中是“sender”,我们用 this
替换参数“sender”——即 PropertyChangedEventHandler
的第一个参数。
这是必要的,因为否则,传递给 PropertyChanged
的 this
引用将是“基”Person
对象的引用,而不是派生 SelectablePerson
对象的引用,因此 WPF 绑定将不会注意到 SelectablePerson
的 IsItemSelected
属性的变化。
在下一个示例中,我们将展示使用 OneWayEventWraps
属性包装 ItemSelectedChangedEvent
也是更好的选择。
以非常类似的方式,SelectableCar
“派生”自 Selectable
和 Car
类
[OneWayEventWraps(typeof(Selectable), "PropertyChanged", "sender")] [Wraps(typeof(Selectable), WrappedItemsKind.Event, "ItemSelectedChangedEvent")] [Wraps(typeof(Selectable), WrappedItemsKind.Property, "IsItemSelected")] [Wraps(typeof(Selectable), WrappedItemsKind.Method, "ToggleIsSelected")] [Wraps(typeof(Car), WrappedItemsKind.Property, "Make", "Model")] public partial class SelectableCar : ISelectable, INotifyPropertyChanged { public SelectableCar(string make, string model) { this.TheSelectable = new Selectable(); this.TheCar = new Car { Make = make, Model = model}; } }
现在,看看 CollectionWithUniqueItemSelection<T>
类。此类在《基于 View-ViewModel 的 WPF 和 XAML 实现模式》文章中有详细描述。此集合为其项定义了唯一的选中行为,提供了所需的选中和取消选中功能。
CollectionWithUniqueItemSelection<T>
派生自 ObservableCollection<T>
,其中 T
应该是 ISelectable
。CollectionWithUniqueItemSelection<T>
类还定义了 SelectedItem
属性——当没有项被选中时,它为 null;否则,它被设置为当前选中的项。
类中的代码确保 SelectedItem
始终指向 IsItemSelected
属性设置为 true 的唯一项。这是此类完整的代码
public class CollectionWithUniqueItemSelection<T> : ObservableCollection<T> where T : class, ISelectable { T _selectedItem = null; public T SelectedItem { get { return _selectedItem; } set { if (_selectedItem == value) return; if (_selectedItem != null) // unselect old item { _selectedItem.IsItemSelected = false; } _selectedItem = value; if (_selectedItem != null) // select the new item { _selectedItem.IsItemSelected = true; } OnPropertyChanged(new PropertyChangedEventArgs("SelectedItem")); } } void Init() { ConnectItems(this); this.CollectionChanged += CollectionWithUniqueItemSelection_CollectionChanged; } void CollectionWithUniqueItemSelection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { DisconnectItems(e.OldItems); ConnectItems(e.NewItems); } void DisconnectItems(IEnumerable items) { if (items == null) return; foreach (T item in items) { item.ItemSelectedChangedEvent -= item_ItemSelectedChangedEvent; } } void ConnectItems(IEnumerable items) { if (items == null) return; foreach (T item in items) { item.ItemSelectedChangedEvent += item_ItemSelectedChangedEvent; } } void item_ItemSelectedChangedEvent(ISelectable item) { if (item.IsItemSelected) { this.SelectedItem = (T)item; } else { this.SelectedItem = null; } } public CollectionWithUniqueItemSelection() { Init(); } public CollectionWithUniqueItemSelection(IEnumerable<T> collection) : base(collection) { Init(); } }
我们通过派生自 CollectionWithUniqueItemSelection<T>
并分别用 SelectablePerson
和 SelectableCar
对象填充它们来构建我们的 ViewModel。
public class MyTestSelectablePersonCollection : CollectionWithUniqueItemSelection{ public MyTestSelectablePersonCollection() { Add(new SelectablePerson("Joe Doe")); Add(new SelectablePerson("Jane Dane")); Add(new SelectablePerson("Jack Flack")); Add(new SelectablePerson("Judi Moodi")); } }
public class MyTestSelectableCarCollection : CollectionWithUniqueItemSelection{ public MyTestSelectableCarCollection() { Add(new SelectableCar("Ford", "Mustang")); Add(new SelectableCar("Chevy", "Cavalier")); Add(new SelectableCar("Toyota", "Corolla")); Add(new SelectableCar("Honda", "Civic")); } }
现在看一下 MainWindow.xaml 文件。我们在其 Resources
部分的顶部定义了我们的 ViewModel。
<local:MyTestSelectablePersonCollection x:Key="TheTestSelectablePersonCollection" />
<local:MyTestSelectableCarCollection x:Key="TheTestSelectableCarCollection" />
按钮行在文件底部定义为 ItemControl
。
<ItemsControl x:Name="PersonSelectionControl"
Style="{StaticResource SelectableItemsControlStyle}"
ItemsSource="{StaticResource TheTestSelectablePersonCollection}"
local:AttachedProps.ItemInternalDataTemplateProperty="{StaticResource PersonDataTemplate}" />
<ItemsControl x:Name="CarSelectionControl"
Style="{StaticResource SelectableItemsControlStyle}"
ItemsSource="{StaticResource TheTestSelectableCarCollection}"
local:AttachedProps.ItemInternalDataTemplateProperty="{StaticResource CarDataTemplate}"
Grid.Row="1"/>
SelectableItemControlStyle
样式使用 SelectableItemDataTemplate
资源为单个项定义 ItemTemplate
。
<DataTemplate x:Key="SelectableItemDataTemplate">
<Grid Background="Transparent"
x:Name="ItemPanel">
<Border x:Name="TheItemBorder"
Background="Black"
BorderBrush="White"
BorderThickness="1">
<ContentControl Content="{Binding}"
ContentTemplate="{Binding Path=(local:AttachedProps.ItemInternalDataTemplate),
RelativeSource={RelativeSource AncestorType=ItemsControl}}" />
</Border>
<Border x:Name="TheOpacityBorder"
Background="White"
Opacity="0.5" />
<i:Interaction.Triggers>
<!-- Call ToggleSelection method when when MouseDown event
is fired on the item -->
<i:EventTrigger EventName="MouseDown">
<ei:CallMethodAction MethodName="ToggleIsSelected"
TargetObject="{Binding Path=DataContext, ElementName=ItemPanel}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
<DataTemplate.Triggers>
<!-- make TheOpacityBorder less opaque when the mouse is over-->
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="TheOpacityBorder"
Property="Opacity"
Value="0.3" />
</Trigger>
<DataTrigger Binding="{Binding Path=IsItemSelected}"
Value="True">
<!-- make TheOpacityBorder completely transparent when the
item is selected-->
<Setter TargetName="TheOpacityBorder"
Property="Opacity"
Value="0" />
<!-- make the ItemPanel non-responsive to the event when
the item is selected (this is to prevent unselection
when clicking on the same item again) -->
<Setter TargetName="ItemPanel"
Property="IsHitTestVisible"
Value="False" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
正如您所见,每个“按钮”的亮/暗度是通过数据触发器根据 IsItemSelected
属性是 true
还是 false
来控制的。
<DataTrigger Binding="{Binding Path=IsItemSelected}"
Value="True">
<!-- make TheOpacityBorder completely transparent when the
item is selected-->
<Setter TargetName="TheOpacityBorder"
Property="Opacity"
Value="0" />
<!-- make the ItemPanel non-responsive to the event when
the item is selected (this is to prevent unselection
when clicking on the same item again) -->
<Setter TargetName="ItemPanel"
Property="IsHitTestVisible"
Value="False" />
</DataTrigger>
此外,使用 Microsoft Expression Blend SDK 触发器,每当项被单击时,我们都会在相应项的 ViewModel 上调用 ToggleIsSelected()
方法。
<i:Interaction.Triggers>
<!-- Call ToggleSelection method when when MouseDown event
is fired on the item -->
<i:EventTrigger EventName="MouseDown">
<ei:CallMethodAction MethodName="ToggleIsSelected"
TargetObject="{Binding Path=DataContext, ElementName=ItemPanel}" />
</i:EventTrigger>
</i:Interaction.Triggers>
此外,每个“按钮”的内容由附加的 AttachedProps.ItemInternalDataTemplate
属性确定。
<Border x:Name="TheItemBorder"
Background="Black"
BorderBrush="White"
BorderThickness="1">
<ContentControl Content="{Binding}"
ContentTemplate="{Binding Path=(local:AttachedProps.ItemInternalDataTemplate),
RelativeSource={RelativeSource AncestorType=ItemsControl}}" />
</Border>
对于 Person
行,此属性设置为 PersonDataTemplate
,对于 Car
行,则设置为 CarDataTemplate
。
<DataTemplate x:Key="PersonDataTemplate">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White"
Text="{Binding Path=Name}"
Margin="10, 5" />
</DataTemplate>
<DataTemplate x:Key="CarDataTemplate">
<StackPanel Orientation="Horizontal"
Margin="5">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White"
FontWeight="Bold"
Text="{Binding Path=Make}"
Margin="5, 0" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="Green"
Text="{Binding Path=Model}"
Margin="5, 0" />
</StackPanel>
</DataTemplate>
运行应用程序时,您可以看到 CollectionWithUniqueItemSelection
类正确地强制每行最多只有一个选中的项,尽管 SelectablePerson
和 SelectableCar
类都不是(在严格的 C# 意义上)派生自 Selectable
!这意味着多态性在基于包装器的继承中有效!
唯一选择和选中项显示示例
当您运行 SelectedItemDisplaySample
项目并单击某些按钮时,您将看到以下内容
右侧将显示选定人员的姓名。
SelectedItemDisplaySample
的代码与上一个示例非常相似(除了缺少与 Car 相关的类)。
MainWindow.xaml 文件底部有一个小补充——我们添加了显示选定人员姓名的功能。
<Grid Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DataContext="{Binding Source={StaticResource TheTestSelectablePersonCollection}}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Selected Person:"/>
<TextBlock Grid.Row="1"
Text="{Binding SelectedItem.Name}" />
</Grid>
正如您所见,底部的 TextBlock
绑定到了 CollectionWithUniqueItemSelection
的 SelectedItem.Name
路径。
现在,如果您查看 SelectablePerson
类,您会发现一个有趣的更改——ItemSelecteChangedEvent
现在使用 OneWayEventWraps
属性定义,而不是像上一个示例那样使用 Wraps
属性。
[OneWayEventWraps(typeof(Selectable), "PropertyChanged", "sender")] [OneWayEventWraps(typeof(Selectable), "ItemSelectedChangedEvent", "obj")] //[Wraps(typeof(Selectable), WrappedItemsKind.Event, "ItemSelectedChangedEvent")] [Wraps(typeof(Selectable), WrappedItemsKind.Property, "IsItemSelected")] [Wraps(typeof(Selectable), WrappedItemsKind.Method, "ToggleIsSelected")] [Wraps(typeof(Person), WrappedItemsKind.Property, "Name")] public partial class SelectablePerson : ISelectable, INotifyPropertyChanged { public SelectablePerson(string name) { this.TheSelectable = new Selectable(); this.ThePerson = new Person { Name = name }; } }
事实上,如果您尝试取消注释 Wraps
并注释掉 OneWayEventWraps
行,该示例将停止工作。原因与上面关于 PropertyChanged
事件的解释以及《基于 Roslyn 的模拟多重继承用法模式 (第一部分)》文章中的解释非常相似。
如果没有 OneWayEventWraps
属性,ItemSelectedChangedEvent
会将 SelectedItem
设置为 Selectable
“基”对象,而不是完整的 SelectablePerson
对象。Selectable
“基”对象没有 Name
属性,因此不会显示任何内容。然而,OneWayEventWraps
属性会强制将 SelectedItem
属性设置为正确的 SelectablePerson
对象。
可选项和可描述项示例
在此示例中,我们将展示我们可以“继承”自三个类而不是两个类,而且这三个“部分”组合在一起也能正常工作。
要开始示例,请打开并运行 SelectableAndDescribableItemSample
项目。您将看到以下内容(单击一些按钮后)
现在,每个 Person
项在右侧的 TextBox
中都有一个描述。此外,您可以更改描述,单击其他按钮,然后再次单击同一人,您将看到描述的更改已被记录。
此示例的代码与上一个示例非常相似。它有一个额外的类 Descrabable
,其中包含一个 Description
属性,该属性在更改时触发 PropertyChanged
事件。
public class Describable : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; void OnPropertyChanged(string propName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propName)); } #region Description Property private string _description; public string Description { get { return this._description; } set { if (this._description == value) { return; } this._description = value; this.OnPropertyChanged("Description"); } } #endregion Description Property }
SelectableDesribablePerson
类对 Selectable
的 ItemSelectedChangedEvent
以及 Selectable
和 Describable
“超类”的 PropertyChanged
事件使用 OneWayEventWraps
属性。
[OneWayEventWraps(typeof(Selectable), "ItemSelectedChangedEvent", "obj")] [OneWayEventWraps(typeof(Selectable), "PropertyChanged", "sender")] [OneWayEventWraps(typeof(Describable), "PropertyChanged", "sender")] [Wraps(typeof(Selectable), WrappedItemsKind.Property, "IsItemSelected")] [Wraps(typeof(Selectable), WrappedItemsKind.Method, "ToggleIsSelected")] [Wraps(typeof(Describable), WrappedItemsKind.Property, "Description")] [Wraps(typeof(Person), WrappedItemsKind.Property, "Name")] public partial class SelectableDescribablePerson : ISelectable, INotifyPropertyChanged { public SelectableDescribablePerson(string name, string description = null) { this.TheSelectable = new Selectable(); this.TheDescribable = new Describable { Description = description }; this.ThePerson = new Person { Name = name }; } }
实现菱形继承
菱形继承发生在两个类继承自一个类,然后第四个类继承自这两个类时。
我们可能希望 SelectableBatman
类继承自 SelectableBat
和 SelectableMan
,而不是从 Sectable
、Bat
和 Man
继承,这有多种原因。
主要原因是,我们可能希望在 SelectableBat
中保留 Selectable
和 Bat
之间的某些连接,在 SelectableMan
中保留 Selectable
和 Man
之间的连接。
其他原因可能涉及封装,例如,如果 Selectable
可能是一个内部类,并且我们无法访问它进行派生,或者在我们的宇宙中 Bat
和 Man
对象总是可选项,而不需要不可选项的 Bat
和 Man
类。
在 C++ 中,菱形继承是通过关键字 virtual
实现的。该关键字必须在 SelectableBat
或 SelectableMan
中的一个继承自 Selectable
的早期阶段出现。这很不方便,而且完全不必要——您必须选择哪个类将依赖于另一个类的实现,然后您才能真正需要创建 SelectableBatman
类。此外,如果您虚拟继承 SelectableBat
而非虚拟继承 SelectableMan
,那么将来是否需要创建其他组合,其中 SelectableBat
将提供自己的 Selectable
实现,而 SelectableMan
反之亦然?
我们将展示如何使用我们的包装器继承来解决这个问题。
菱形继承示例位于 DiamondInheritanceSample
项目下。
这是您在运行项目并单击一些按钮时看到的内容
现在,让我们看一下代码。
Selectable
类与先前项目中的类完全相同。
Bat
类只有一个简单的属性 IsFlying
,表示 Bat
对象当前是否正在飞行。
public class Bat { public bool IsFlying { get; set; } }
有人可能会注意到,IsFlying
属性实际上应该在更改时触发 PropertyChanged
事件以显示某些视觉变化,但我们试图让示例尽可能轻量级——因此对我们的目的来说,这并不必要。
Man
类有两个简单的属性:Name
和 IsWalking
——前者指定人的姓名,后者指定人当前是否正在行走。
public class Man { public string Name { get; set; } public bool IsWalking { get; set; } }
SelectableBat
派生自 Selectable
和 Bat
。
[OneWayEventWraps(typeof(Selectable), "PropertyChanged", "sender")] [Wraps(typeof(Selectable), WrappedItemsKind.Event, "ItemSelectedChangedEvent")] [Wraps(typeof(Selectable), WrappedItemsKind.Property, "IsItemSelected")] [Wraps(typeof(Selectable), WrappedItemsKind.Method, "ToggleIsSelected")] [Wraps(typeof(Bat), WrappedItemsKind.Property, "IsFlying")] public partial class SelectableBat { }
SelectableMan
派生自 Selectable
和 Man
。
[OneWayEventWraps(typeof(Selectable), "PropertyChanged", "sender")] [Wraps(typeof(Selectable), WrappedItemsKind.Event, "ItemSelectedChangedEvent")] [Wraps(typeof(Selectable), WrappedItemsKind.Property, "IsItemSelected")] [Wraps(typeof(Selectable), WrappedItemsKind.Method, "ToggleIsSelected")] [Wraps(typeof(Man), WrappedItemsKind.Property, "IsWalking", "Name")] public partial class SelectableMan { }
SelectableBatman
派生自 SelectableBat
和 SelectableMan
,并且 Selectable
部分的共享在 SelectableBatman
的构造函数中得到保证。
[OneWayEventWraps(typeof(SelectableMan), "PropertyChanged", "sender")] [Wraps(typeof(SelectableMan), WrappedItemsKind.Event, "ItemSelectedChangedEvent")] [Wraps(typeof(SelectableMan), WrappedItemsKind.Property, "IsItemSelected")] [Wraps(typeof(SelectableMan), WrappedItemsKind.Method, "ToggleIsSelected")] [Wraps(typeof(SelectableMan), WrappedItemsKind.Property, "IsWalking", "Name")] [Wraps(typeof(SelectableBat), WrappedItemsKind.Property, "IsFlying")] public partial class SelectableBatMan : ISelectable, INotifyPropertyChanged { public SelectableBatMan(string name, bool isWalking, bool isFlying) { // shared Selectable object Selectable selectable = new Selectable(); // pass the same Selectable to SelectableMan // 'super-class' this.TheSelectableMan = new SelectableMan { TheMan = new Man { Name = name, IsWalking = isWalking, }, TheSelectable = selectable }; // and pass the same Selectable to // SelectableBat 'super-class' this.TheSelectableBat = new SelectableBat { TheBat = new Bat { IsFlying = isFlying}, TheSelectable = selectable }; } }
正如您所看到的——我们在构造函数中创建一个单一的 Selectable
对象,并将其传递给 SelectableMan
和 SelectableBat
的“基类”构造函数。
这就是确保 SelectableBatman
的 SelectableBat
和 SelectableMan
部分共享 Selectable
部分所需的一切!
结论
我们已经展示了如何在 C# 中使用自动生成的包装器实现多重继承。我们还表明,通过使用接口可以保留多态性或基于包装器的子类。
最后,我们提供了一个示例,展示了使用生成的包装器实现著名的菱形继承是多么简单。事实上,我们的解决方案比 C++ 内置的解决方案要好得多——它在创建超类时不会施加任何僵化的限制。