Talkin’’bout my generation:Uno Platform如何生成代码,第2部分—

以前,我们研究了Uno Platform如何将XAML标记文件转换为C#代码。 在本文中,我将讨论Uno使用代码生成的另一种方式,它使我们能够使本机Android和iOS视图符合UWP的API,并解决棘手的多重继承问题。

想要这一切

Uno在Android和iOS上的部分功能是能够轻松地将UWP视图类型与纯本地视图混合在一起。 这是可能的,因为在Uno中,所有视图都继承自本机基本视图类型:Android上的视图 ,iOS上的UIView

但是,正如我在之前的文章中提到的那样,这对再现UWP的继承层次结构构成了挑战。 UIElement是UWP中的原始视图类型,但它又从DependencyObject类派生。 DependencyObject是具有DependencyProperties所有对象(即支持数据绑定的任何对象)的基类。 这包括所有视图以及一些非视图框架类型,例如TransformsBrushes

我们想从ViewGroupUIView继承。 我们还想从DependencyObject.继承DependencyObject. C#不允许多重继承,那么我们该怎么办? 由于我们无法更改iOS或Android框架,因此我们在Uno中选择将DependencyObjectinterface 。 这样就可以将Uno FrameworkElement用作UIView并同时将其作为DependencyObject 。 但是,仅凭这一点还不够。

如果您的应用中有这样的代码怎么办?

public class MyBindableObject : DependencyObject 
    { 
        public string MyProperty 
        { 
            get { return (string)GetValue(MyPropertyProperty); } 
            set { SetValue(MyPropertyProperty, value); } 
        } 
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc... 
        public static readonly DependencyProperty MyPropertyProperty = 
            DependencyProperty.Register("MyProperty", typeof(string), typeof(MyBindableObject), new PropertyMetadata(default(string))); 
    }

我们从DependencyObject继承并使用标准语法定义了DependencyProperty ,该语法使用DependencyObject.GetValueDependencyObject.SetValue方法。 在UWP上,这些是在基类中定义的,但是如果DependencyObject是接口, 没有基类。 实际上,如果只是接口,则代码不会编译,因为尚未实现该接口。

幸运的是, DependencyObject不仅 Uno中的接口,而且上面的代码将像在UWP上一样按原样在Android和iOS上进行编译。 代码生成使之成为现实。 这是一些程序员的技巧来说明这一点。 详细说明如下。

在UWP上, UIElement 继承自 DependencyObject 类。

多重继承-不是一种选择。

在Uno中, DependencyObject 是一个接口,其实现由代码生成自动提供。

在其他情况下,我们也面临这种问题的较弱形式-希望具有两种基本类型。 在框架的某些地方,我们从派生的本机视图类型继承。 例如, ScrollContentPresenter继承自Android和iOS上的本机滚动视图。 但是我们也希望ScrollContentPresenter公开FrameworkElement的方法和属性。

通过使用代码生成在C#中实现mixins,我们成功解决了这两个问题。

混合起来

大多数静态类型的语言由于其带来的复杂性(即“钻石问题”)而不允许多个基类。 (C ++是一个显着的例外。)但是,在动态类型的语言中,使用mixins以可重用的方式将额外的功能附加到类上是很常见的。

C#作为一种静态类型的语言,不支持将mixins作为一流的语言功能。 但是,代码生成使我们可以对其进行仿真。 Uno使用代码生成以(至少)两种不同的方式添加混合。

我将从更简单的方法开始:使用“ T4”模板。 引用Microsoft的文档:

在Visual Studio中,T4文本模板是文本块和可以生成文本文件的控制逻辑的混合体。 在Visual C#或Visual Basic中,控制逻辑被编写为程序代码的片段。 在Visual Studio 2015 Update 2和更高版本中,可以在T4模板指令中使用C#6.0版功能。 生成的文件可以是任何种类的文本,例如网页,资源文件或任何语言的程序源代码。

来源: https : //docs.microsoft.com/zh-cn/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2017

T4模板(“ .tt文件”)已经存在了一段时间。 它们本质上是静态文本(在我们的例子中是C#代码)和条件逻辑的混合。 这是一个片段:

namespace <#= mixin.NamespaceName #> 
{
public partial class <#= mixin.ClassName #> : IFrameworkElement
{
#if !<#= mixin.IsFrameworkElement #>
/// <summary>
/// Gets the parent of this FrameworkElement in the object tree.
/// </summary>
public DependencyObject Parent => ((IDependencyObjectStoreProvider)this).Store.Parent as DependencyObject;
#endif

#if <#= mixin.HasAttachedToWindow #>
partial void OnAttachedToWindowPartial()
{
OnLoading();
OnLoaded();
}

这来自在Uno中添加IFrameworkElement功能的模板 。 它实现了诸如Width / HeightOpacityStyle等属性。在编译时,模板运行并使用ScrollContentPresenter那些成员和其他几个类(包括FrameworkElement本身)创建一个部分类

T4方法经过了充分的测试,并且在这种情况下效果很好。 但是,它有两个限制:

  1. 它需要手动设置:每个要使用mixin的类都必须明确注册。
  2. 它需要手动标记,以确保生成的代码不会“踩”创作的代码,例如,当创作的代码已经定义Foo()时,通过生成Foo()方法。
  3. 它不支持外部代码。 您无法在应用程序中使用上面的mixin(缺少将模板复制粘贴到应用程序中)。

出于这个原因,为了有一个混入来实现DependencyObject的功能,我们进行了一些更复杂,更神奇的事情。

DependencyObjectGenerator —神奇的实现

Roslyn (也称为“ .NET编译器平台”)的发布对代码生成是一个福音。 通过Roslyn,Microsoft开源了C#编译器,但他们还公开了用于代码分析的强大API。 使用Roslyn,可以轻松访问编译器拥有的所有语法和语义信息。

为了利用此功能来生成代码,我们创建了Uno.SourceGeneration包。 像Uno Platform一样,它是免费和开源的。 它创建了一个构建任务,并允许您根据Roslyn对您的解决方案的分析轻松地添加生成的代码。 这可能是增加现有类型的局部类定义,也可能是全新的类。

在Uno中,这由DependencyObjectGenerator类使用。 该生成器在解决方案中寻找实现DependencyObject接口的每个类,例如上面的MyBindableObject示例。 对于每个这样的类,它将自动生成DependencyObject的方法和属性。

由于生成器具有来自Roslyn的全套语义信息,因此它可以以一种聪明的方式做到这一点。 例如,如果它检测到该类是视图类型,则它将添加方法以在加载或卸载视图时更新绑定信息。

这是DependencyObjectGenerator一小段代码

private void WriteAndroidAttachedToWindow(INamedTypeSymbol typeSymbol, IndentedStringBuilder builder)
{
var isAndroidView = typeSymbol.Is(_androidViewSymbol);
var isAndroidActivity = typeSymbol.Is(_androidActivitySymbol);
var isAndroidFragment = typeSymbol.Is(_androidFragmentSymbol);
var isUnoViewGroup = typeSymbol.Is(_unoViewgroupSymbol);
var implementsIFrameworkElement = typeSymbol.Interfaces.Any(t => t == _iFrameworkElementSymbol);
var hasOverridesAttachedToWindowAndroid = isAndroidView &&
typeSymbol
.GetMethods()
.Where(m => IsNotDependencyObjectGeneratorSourceFile(m))
.None(m => m.Name == "OnAttachedToWindow");
				if (isAndroidView || isAndroidActivity || isAndroidFragment)
{
if (!isAndroidActivity && !isAndroidFragment)
{
WriteRegisterLoadActions(typeSymbol, builder);
}
					builder.AppendLine($@"
#if {hasOverridesAttachedToWindowAndroid} //Is Android view (that doesn't already override OnAttachedToWindow)
#if {isUnoViewGroup} //Is UnoViewGroup
// Both methods below are implementation of abstract methods
// which are called from onAttachedToWindow in Java.
protected override void OnNativeLoaded()
{{
_loadActions.ForEach(a => a.Item1());
BinderAttachedToWindow();
}}

在此方法中,我们有一个INamedTypeSymbol ,它是Roslyn的一个对象,其中封装了有关类型的信息。 我们已经确定typeSymbol实现DependencyObject ; 在这里,我们检查它是否为Android View ,如果是,则覆盖已加载的方法。 您可能会注意到,我们还在检查类型是否尚未覆盖相同的方法,因此我们不会意外生成与创作代码冲突并导致编译器错误的代码。 每当您的应用程序编译时,所有这些都在后台进行,而无需用户干预。

最终结果是,即使是接口而不是类, DependencyObject可以在Uno中与UWP几乎完全相同地使用! 有一些极端的情况:例如,一些通用约束不会以相同的方式起作用。 但总的来说,它的效果非常好。

目前为止就这样了。 让我们知道您想进一步了解Uno的其他“内幕”方面!

From: https://hackernoon.com/talkin-bout-my-generation-how-the-uno-platform-generates-code-part-2-under-the-hood-9970ac38ad06

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值