前言
以前做一個金融軟體專案,軟體要英文、繁體版本,開始甲方弄了好幾個月,手動一條一條替換,發現很容易出錯,因為有金融專業術語,字串在不同語意要特殊處理,第三方工具沒法使用,最后我用Roslyn寫了一個工具,只需10分鐘就能翻譯整個軟體
做完上個專案發現Roslyn還可以深度開發,寫了一個工具:代碼助手,可解決專案所有瑣碎重復性操作,代碼完全自動化
原理
Roslyn是什么?
XmlDocument,XDocument可以決議xml,同樣 Roslyn 可決議專案中C#代碼,c#常用插件ReSharper,只能重構一些很規范的代碼(生成IEqualityComparer,IComparer介面...),用Roslyn可以自動化業務代碼
自己寫 代碼助手

一,類->視圖->增刪改查 全自動
需求:一個資料庫所有表的增刪改查
實作:Roslyn+T4實作類自動生成增刪改查界面

- 生成最終效果

- 每個屬性對應不同的控制元件:

- 自動生成單個物件編輯預覽

Roslyn+T4實作
- EnvDTE獲取當前打開專案
- Roslyn api CodeAnalysis 分析當前專案
- 獲取代碼Symbol
- T4代碼自動化
- T4主要代碼
<#@ include file="Include\base.t4" #>
<#@ include file="Include\CodeAnalysis.t4" #>
<#
param.Task = DoAsync();
#>
<#+
Dictionary<string, ClassEntry> dic;
int depthLevel = 2; //嵌套類展開深度
async Task<string> DoAsync()
{
var Modules = "Modules";
var ns = "Test.Database"; //獲取名稱空間 Test.Database 所有類
var DbContextName = "sakilaEntities"; //測驗資料庫
var skipClass = new[]
{
DbContextName,
}.ToHashSet(); //排除類
var modulesProjectItem = new ProjectItemEntry(@"Test\View\Modules"); //獲取專案view的路徑
var viewModelProjectItem = new ProjectItemEntry(@"Test\ViewModel\Modules"); //獲取專案ViewModel的路徑
/*
modulesProjectItem.Delete();
viewModelProjectItem.Delete();
return "";
*/
var analysisCore = await param.TryAnalysisSolutionAsync(cancellationToken); //Roslyn 分析當前工程
var solution = analysisCore.Workspace.CurrentSolution; //Roslyn 的解決方案
await TypeSymbols.InitializeTypeSymbolsAsync(solution, cancellationToken);
//獲取名稱空間 Test.Database 所有類
var list = await solution.GetAllSymbolsAsync((project, symbol) =>
{
var displayString = symbol.ContainingNamespace.ToDisplayString();
if (displayString == ns && !skipClass.Contains(symbol.Name))
{
return true;
}
return false;
});
var items = list.Select(p =>
{
var entry = new ClassEntry(p);
entry.DoProperty();
return entry;
}).ToList();
#>
- T4生成文本
<#+
//xaml
{
var notMapHashSet = new HashSet<string>(); //每個屬性型別對應的控制元件,如果沒有映射,寫日志,再運行
double index = 0;
dic = items.ToDictionary(p=> p.Name);
foreach (var c in items)
{
index++;
var xamlOutput = StartNewFile(modulesProjectItem.GetRelativePath(c.MainViewPair.XamlFileName), true); //打開一個新檔案
param.Log($@"{index / items.Count:P1}:{c.MainViewPair.ClassName}");
//xaml
#>
<UserControl
x:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:custcombobox="clr-namespace:Test.View.CustComboBox"
xmlns:local="clr-namespace:<#=modulesProjectItem.Namespace#>"
xmlns:vm="clr-namespace:<#=viewModelProjectItem.Namespace#>"
d:DataContext="{d:DesignInstance IsDesignTimeCreatable=True,
Type={x:Type vm:<#=c.MainViewPair.ViewModelClassName#>}}"
d:DesignHeight="450"
d:Design
mc:Ignorable="d"
>
..........................................
- 型別->控制元件映射
<#+ if(type.IsType(SpecialType.System_String)){#>
<DataGridTextColumn
Binding="{Binding Result.<#=property.Name #>, UpdateSourceTrigger=PropertyChanged}"
Header="<#=property.NameZh #>"
>
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="ToolTip" Value="https://www.cnblogs.com/metoget/p/<#=property.Name#>" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<#+ } else if(type.IsType(SpecialType.System_Int32)){#>
<DataGridTemplateColumn Header="<#=property.NameZh #>">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<xctk:IntegerUpDown ToolTip="<#=property.Name#>" Value="https://www.cnblogs.com/metoget/p/{Binding Result.<#=property.Name #>, UpdateSourceTrigger=PropertyChanged}">
</xctk:IntegerUpDown>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock ToolTip="<#=property.Name#>" Text="{Binding Result.<#=property.Name #>, Mode=OneWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<#+ } else if(type.IsType(SpecialType.System_Byte)){#>
<DataGridTemplateColumn Header="<#=property.NameZh #>">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<xctk:ByteUpDown ToolTip="<#=property.Name#>" Value="https://www.cnblogs.com/metoget/p/{Binding Result.<#=property.Name #>, UpdateSourceTrigger=PropertyChanged}">
</xctk:ByteUpDown>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock ToolTip="<#=property.Name#>" Text="{Binding Result.<#=property.Name #>, Mode=OneWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<#+ } else if(type.IsType(SpecialType.System_Decimal)){#>
........
- t4涉及類
<#+
public class XamlCsPair
{
public readonly string ClassName = string.Empty; //類名
public readonly string XamlFileName = string.Empty; //View絕對路徑
public readonly string CsFileName = string.Empty; //ViewModel絕對路徑
public readonly string ViewModelClassName = string.Empty; //View類名
public readonly string ViewModelFileName = string.Empty; //ViewModel 檔案名
public XamlCsPair(string className, string vewModelClassName)
{
ClassName = className;
className = className.GetValidFileName("_"); //類名到檔案名,移除非法字符
XamlFileName = $"{className}.xaml";
CsFileName = $"{XamlFileName}.cs";
ViewModelClassName = vewModelClassName;
ViewModelFileName = $"{ViewModelClassName}.cs";
}
}
public class ClassEntry
{
public ProjectSymbolEntry Entry { get; }
public INamedTypeSymbol Symbol { get; } //類Symbol
public readonly string Name = string.Empty; //類的名稱
public readonly string NameZh = string.Empty; //類的中文名稱
public readonly ITypeSymbol Type; //類的型別Symbol
public readonly string TypeName = string.Empty; //類的型別名稱
public readonly string ClassName = string.Empty; //目標類名
public readonly XamlCsPair MainViewPair;
public readonly XamlCsPair EditViewPair;
public List<PropertyEntry> Properties { get; set; } = new List<PropertyEntry>();//類的屬性集合
public ClassEntry(ProjectSymbolEntry entry)
{
Entry = entry;
var symbol = entry.Symbol;
Symbol = symbol;
Name = symbol.Name;
NameZh = Name.ToZh(cacheMode: CacheMode.OneWay); //百度api英文翻譯中文
Type = symbol.GetTypeSymbol();
TypeName = Type.GetDisplayShortName();
ClassName = Name.ToValidIdentifier(removeChars:"_".ToCharArray());
MainViewPair = new XamlCsPair($"{ClassName}View", $"{ClassName}ViewModel");
EditViewPair = new XamlCsPair($"{ClassName}ViewEditorWindow", $"{ClassName}ViewEditorViewModel");
}
public void DoProperty()
{
Properties.Clear();
foreach (var member in Symbol.GetMembers().OfType<IPropertySymbol>())
{
Properties.Add(new PropertyEntry(this, member));
}
}
}
public class PropertyEntry
{
public ClassEntry Class { get; }
public IPropertySymbol Symbol { get; } //屬性的Symbol
public readonly string Name = string.Empty;
public readonly string NameZh = string.Empty; //屬性中文名稱
public readonly ITypeSymbol Type; //屬性型別
public readonly string TypeName = string.Empty; //屬性型別名稱
public PropertyEntry(ClassEntry entry, IPropertySymbol symbol)
{
Class = entry;
Symbol = symbol;
Name = symbol.Name;
NameZh = Name.ToZh(cacheMode: CacheMode.OneWay);//百度api英文翻譯中文
Type = symbol.GetTypeSymbol(); //屬性Roslyn型別
TypeName = Type.GetDisplayShortName(); //屬性Roslyn名稱
}
}
public class ProjectItemEntry
{
public ProjectItem ProjectItem { get; } //dte對應的一個專案檔案
public string Namespace { get; } //ProjectItem的命名空間
public string FileName { get; } //專案檔案絕對路徑
public ProjectItemEntry(string projectRelativePath)
{
ProjectItem = dte.GetProjectItemByRelativePath(projectRelativePath).TryCreateDir();
Namespace = (string)ProjectItem.Properties.Item("DefaultNamespace").Value;
FileName = ProjectItem.GetFileName();
}
public void Delete()
{
ProjectItem.DeleteChildren();
FileName.DeleteSubFiles();
}
public string GetRelativePath(string relativePath)
{
return Path.Combine(FileName, relativePath);
}
}
#>

- 自動生成專案檔案

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/373644.html
標籤:C#
