diff --git a/TODOnewNR.txt b/TODOnewNR.txt index 3b7cb7e57c..72d50a1672 100644 --- a/TODOnewNR.txt +++ b/TODOnewNR.txt @@ -1,10 +1,7 @@  Commented code, needs to be ported and re-enabled: - AssemblyParserService ParseProjectContent - ProjectContentRegistryDescriptor Ambiences - Tooltips (DebuggerService.GetText) DefinitionViewPad Class Browser (removed from source tree; to be reimplemented using WPF) NRefactoryLanguageConverter @@ -29,7 +26,7 @@ Stuff that was renamed/moved: Functionality changes: The result of a parser run (ParseInformation) now may contain a fully parsed AST. The ParserService may cache such full ASTs, but may also drop them from memory at any time. - This is implemented by keeping the last N accessed files in the cache. + This will be implemented by keeping the last N accessed files in the cache. (currently we just keep the caches around forever) Every parse information also contains an IParsedFile instance with the type system information. The IParsedFile is stored permanently (both in ParserService and in the IProjectContents). diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Parser/CSharpSymbolSearch.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Parser/CSharpSymbolSearch.cs index 9fe355219f..621b2bd315 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Parser/CSharpSymbolSearch.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Parser/CSharpSymbolSearch.cs @@ -63,8 +63,7 @@ namespace CSharpBinding } if (canReferenceEntity) { - var allTypesInPC = TreeTraversal.PreOrder(ctx.GetTypes().Where(t => t.ProjectContent == pc), t => t.NestedTypes); - interestingFileNames[i] = fr.GetInterestingFileNames(searchScopes[i], allTypesInPC, ctx); + interestingFileNames[i] = fr.GetInterestingFileNames(searchScopes[i], pc.GetAllTypes(), ctx); workAmount += interestingFileNames[i].Count; } else { interestingFileNames[i] = new string[0]; diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index d6ae212c9b..608914b319 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -369,6 +369,7 @@ + diff --git a/src/Main/Base/Project/Src/Editor/Commands/FindDerivedClassesOrOverrides.cs b/src/Main/Base/Project/Src/Editor/Commands/FindDerivedClassesOrOverrides.cs index b91fa72bcc..edcc2260a9 100644 --- a/src/Main/Base/Project/Src/Editor/Commands/FindDerivedClassesOrOverrides.cs +++ b/src/Main/Base/Project/Src/Editor/Commands/FindDerivedClassesOrOverrides.cs @@ -1,9 +1,10 @@ // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) -/* + using System; using ICSharpCode.Core; -using ICSharpCode.SharpDevelop.Dom; +using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Refactoring; @@ -16,15 +17,13 @@ namespace ICSharpCode.SharpDevelop.Editor.Commands { protected override void RunImpl(ITextEditor editor, int offset, ResolveResult symbol) { - var classUnderCaret = GetClass(symbol); - if (classUnderCaret != null) { - ContextActionsHelper.MakePopupWithDerivedClasses(classUnderCaret).OpenAtCaretAndFocus(); + IEntity entityUnderCaret = GetEntity(symbol); + if (entityUnderCaret is ITypeDefinition && !entityUnderCaret.IsSealed) { + ContextActionsHelper.MakePopupWithDerivedClasses((ITypeDefinition)entityUnderCaret).OpenAtCaretAndFocus(); return; } - var memberUnderCaret = GetMember(symbol); - if (memberUnderCaret != null && memberUnderCaret.IsOverridable) - { - ContextActionsHelper.MakePopupWithOverrides(memberUnderCaret).OpenAtCaretAndFocus(); + if (entityUnderCaret is IMember && ((IMember)entityUnderCaret).IsOverridable) { + ContextActionsHelper.MakePopupWithOverrides((IMember)entityUnderCaret).OpenAtCaretAndFocus(); return; } MessageService.ShowError("${res:ICSharpCode.Refactoring.NoClassOrOverridableSymbolUnderCursorError}"); @@ -42,4 +41,3 @@ namespace ICSharpCode.SharpDevelop.Editor.Commands // } } } -*/ \ No newline at end of file diff --git a/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs b/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs index 83899ea637..717e9f6305 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs @@ -66,15 +66,33 @@ namespace ICSharpCode.SharpDevelop.Parser /// public static ITypeResolveContext CurrentTypeResolveContext { get { - IProject currentProject = ProjectService.CurrentProject; - if (currentProject != null) { - return currentProject.TypeResolveContext ?? GetDefaultTypeResolveContext(); - } else { - return GetDefaultTypeResolveContext(); - } + return GetTypeResolveContext(ProjectService.CurrentProject); + } + } + + public static ITypeResolveContext GetTypeResolveContext(IProject project) + { + if (project != null) { + return project.TypeResolveContext; + } else { + return GetDefaultTypeResolveContext(); } } + /// + /// Gets a type resolve context that includes all referenced assemblies for the specified project content. + /// + public static ITypeResolveContext GetTypeResolveContext(IProjectContent projectContent) + { + if (projectContent == null) + return GetDefaultTypeResolveContext(); + IProject p = GetProject(projectContent); + if (p != null) + return p.TypeResolveContext; + else + return projectContent; + } + [Obsolete("Use project.ProjectContent instead")] public static IProjectContent GetProjectContent(IProject project) { diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsHelper.cs b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsHelper.cs index 6c3bb891f6..0734f3402e 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsHelper.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/ContextActions/ContextActionsHelper.cs @@ -5,29 +5,33 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; - using ICSharpCode.Core; using ICSharpCode.Core.Presentation; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Refactoring; -/* namespace ICSharpCode.SharpDevelop.Refactoring { public class ContextActionsHelper { - public static ContextActionsPopup MakePopupWithDerivedClasses(IClass baseClass) + public static ContextActionsPopup MakePopupWithDerivedClasses(ITypeDefinition baseClass) { - var derivedClassesTree = RefactoringService.FindDerivedClassesTree(baseClass); + var derivedClassesTree = FindReferenceService.BuildDerivedTypesGraph(baseClass).ConvertToDerivedTypeTree(); var popupViewModel = new ContextActionsViewModel { Title = MenuService.ConvertLabel(StringParser.Parse( "${res:SharpDevelop.Refactoring.ClassesDerivingFrom}", new StringTagPair("Name", baseClass.Name)))}; - popupViewModel.Actions = new PopupTreeViewModelBuilder().BuildTreeViewModel(derivedClassesTree); + popupViewModel.Actions = new PopupTreeViewModelBuilder().BuildTreeViewModel(derivedClassesTree.Children); return new ContextActionsPopup { Actions = popupViewModel, Symbol = baseClass }; } - public static ContextActionsPopup MakePopupWithBaseClasses(IClass @class) + public static ContextActionsPopup MakePopupWithBaseClasses(ITypeDefinition @class) { - var baseClassList = @class.ClassInheritanceTree.Where( - baseClass => (baseClass != @class) && (baseClass.CompilationUnit != null) && (baseClass.CompilationUnit.FileName != null)); + List baseClassList; + using (var context = ParserService.GetTypeResolveContext(@class.ProjectContent).Synchronize()) { + @class = @class.GetDefinition(); + baseClassList = @class.GetAllBaseTypeDefinitions(context).Where( + baseClass => (baseClass != @class) && (baseClass.ParsedFile != null)).ToList(); + } var popupViewModel = new ContextActionsViewModel { Title = MenuService.ConvertLabel(StringParser.Parse( "${res:SharpDevelop.Refactoring.BaseClassesOf}", new StringTagPair("Name", @class.Name)))}; popupViewModel.Actions = new PopupListViewModelBuilder().BuildListViewModel(baseClassList); @@ -36,14 +40,16 @@ namespace ICSharpCode.SharpDevelop.Refactoring public static ContextActionsPopup MakePopupWithOverrides(IMember member) { - var derivedClassesTree = RefactoringService.FindDerivedClassesTree(member.DeclaringType); + #warning Reimplement MakePopupWithOverrides + throw new NotImplementedException(); + /*var derivedClassesTree = RefactoringService.FindDerivedClassesTree(member.DeclaringType); var popupViewModel = new ContextActionsViewModel { Title = MenuService.ConvertLabel(StringParser.Parse( "${res:SharpDevelop.Refactoring.OverridesOf}", new StringTagPair("Name", member.FullyQualifiedName)) )}; popupViewModel.Actions = new OverridesPopupTreeViewModelBuilder(member).BuildTreeViewModel(derivedClassesTree); - return new ContextActionsPopup { Actions = popupViewModel, Symbol = member }; + return new ContextActionsPopup { Actions = popupViewModel, Symbol = member };*/ } class PopupViewModelBuilder @@ -56,10 +62,10 @@ namespace ICSharpCode.SharpDevelop.Refactoring this.LabelAmbience.ConversionFlags = ConversionFlags.ShowTypeParameterList; } - protected ContextActionViewModel MakeGoToClassAction(IClass @class, ObservableCollection childActions) + protected ContextActionViewModel MakeGoToClassAction(ITypeDefinition @class, ObservableCollection childActions) { return new ContextActionViewModel { - Action = new GoToClassAction(@class, this.LabelAmbience), + Action = new GoToClassAction(@class, this.LabelAmbience.ConvertEntity(@class)), Image = ClassBrowserIconService.GetIcon(@class).ImageSource, Comment = string.Format("(in {0})", @class.Namespace), ChildActions = childActions @@ -69,7 +75,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring class PopupListViewModelBuilder : PopupViewModelBuilder { - public ObservableCollection BuildListViewModel(IEnumerable classList) + public ObservableCollection BuildListViewModel(IEnumerable classList) { return new ObservableCollection( classList.Select(@class => MakeGoToClassAction(@class, null))); @@ -78,7 +84,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring class PopupTreeViewModelBuilder : PopupViewModelBuilder { - public ObservableCollection BuildTreeViewModel(IEnumerable> classTree) + public ObservableCollection BuildTreeViewModel(IEnumerable> classTree) { return new ObservableCollection( classTree.Select( @@ -86,6 +92,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring } } + /* class OverridesPopupTreeViewModelBuilder : PopupViewModelBuilder { IMember member; @@ -97,7 +104,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring this.member = member; } - protected ContextActionViewModel MakeGoToMemberAction(IClass containingClass, ObservableCollection childActions) + protected ContextActionViewModel MakeGoToMemberAction(ITypeDefinition containingClass, ObservableCollection childActions) { var overridenMember = MemberLookupHelper.FindSimilarMember(containingClass, this.member); if (overridenMember == null || overridenMember.Region.IsEmpty) @@ -106,18 +113,17 @@ namespace ICSharpCode.SharpDevelop.Refactoring return new ContextActionViewModel { Action = new GoToMemberAction(overridenMember, this.LabelAmbience), Image = ClassBrowserIconService.GetIcon(overridenMember).ImageSource, - Comment = string.Format("(in {0})", containingClass.FullyQualifiedName), + Comment = string.Format("(in {0})", containingClass.FullName), ChildActions = childActions }; } - public ObservableCollection BuildTreeViewModel(IEnumerable> classTree) + public ObservableCollection BuildTreeViewModel(IEnumerable> classTree) { return new ObservableCollection( classTree.Select( node => MakeGoToMemberAction(node.Content, BuildTreeViewModel(node.Children))).Where(action => action != null)); } - } + }*/ } } -*/ diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs b/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs index c934b69585..724359d46e 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/FindReferenceService.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using ICSharpCode.Core; using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Project; @@ -17,20 +19,26 @@ namespace ICSharpCode.SharpDevelop.Refactoring /// public static class FindReferenceService { - public static void FindReferences(IEntity entity, IProgressMonitor progressMonitor, Action callback) + #region FindReferences + static IEnumerable GetProjectsThatCouldReferenceEntity(IEntity entity) { - if (ParserService.LoadSolutionProjectsThreadRunning) { - if (progressMonitor != null) progressMonitor.ShowingDialog = true; - MessageService.ShowMessage("${res:SharpDevelop.Refactoring.LoadSolutionProjectsThreadRunning}"); - if (progressMonitor != null) progressMonitor.ShowingDialog = false; - return; + Solution solution = ProjectService.OpenSolution; + if (solution == null) + yield break; + foreach (IProject project in solution.Projects) { + IProjectContent pc = project.ProjectContent; + if (pc == null) + continue; + yield return project; } - if (ProjectService.OpenSolution == null) - return; + } + + static List PrepareSymbolSearch(IEntity entity, CancellationToken cancellationToken, out double totalWorkAmount) + { + totalWorkAmount = 0; List symbolSearches = new List(); - double totalWorkAmount = 0; - foreach (IProject project in ProjectService.OpenSolution.Projects) { - progressMonitor.CancellationToken.ThrowIfCancellationRequested(); + foreach (IProject project in GetProjectsThatCouldReferenceEntity(entity)) { + cancellationToken.ThrowIfCancellationRequested(); ISymbolSearch symbolSearch = project.PrepareSymbolSearch(entity); if (symbolSearch != null) { symbolSearches.Add(symbolSearch); @@ -39,6 +47,19 @@ namespace ICSharpCode.SharpDevelop.Refactoring } if (totalWorkAmount < 1) totalWorkAmount = 1; + return symbolSearches; + } + + public static void FindReferences(IEntity entity, IProgressMonitor progressMonitor, Action callback) + { + if (ParserService.LoadSolutionProjectsThreadRunning) { + progressMonitor.ShowingDialog = true; + MessageService.ShowMessage("${res:SharpDevelop.Refactoring.LoadSolutionProjectsThreadRunning}"); + progressMonitor.ShowingDialog = false; + return; + } + double totalWorkAmount; + List symbolSearches = PrepareSymbolSearch(entity, progressMonitor.CancellationToken, out totalWorkAmount); double workDone = 0; ParseableFileContentFinder parseableFileContentFinder = new ParseableFileContentFinder(); foreach (ISymbolSearch s in symbolSearches) { @@ -51,6 +72,98 @@ namespace ICSharpCode.SharpDevelop.Refactoring progressMonitor.Progress = workDone / totalWorkAmount; } } + #endregion + + #region FindDerivedTypes + /// + /// Finds all types that are derived from the given base type. + /// + public static IList FindDerivedTypes(ITypeDefinition baseType, bool directDerivationOnly) + { + if (baseType == null) + throw new ArgumentNullException("baseType"); + baseType = baseType.GetDefinition(); // ensure we use the compound class + + List results = new List(); + + foreach (IProject project in GetProjectsThatCouldReferenceEntity(baseType)) { + using (var ctx = project.TypeResolveContext.Synchronize()) { + foreach (ITypeDefinition typeDef in project.ProjectContent.GetAllTypes()) { + bool isDerived; + if (directDerivationOnly) { + isDerived = false; + foreach (IType t in typeDef.GetBaseTypes(ctx)) { + ITypeDefinition tdef = baseType.GetDefinition(); + if (tdef == baseType) { + isDerived = true; + break; + } + } + } else { + isDerived = typeDef.IsDerivedFrom(baseType, ctx); + } + if (isDerived) + results.Add(typeDef); + } + } + } + return results; + } + + + /// + /// Builds a graph of derived type definitions. + /// + public static TypeGraphNode BuildDerivedTypesGraph(ITypeDefinition baseType) + { + if (baseType == null) + throw new ArgumentNullException("baseType"); + IEnumerable projectContents = GetProjectsThatCouldReferenceEntity(baseType).Select(p => p.ProjectContent); + projectContents = projectContents.Union(new [] { baseType.ProjectContent }); + var graph = BuildTypeInheritanceGraph(projectContents); + TypeGraphNode node; + if (graph.TryGetValue(baseType, out node)) { + node.BaseTypes.Clear(); // only derived types were requested, so don't return the base types + return node; + } else { + return new TypeGraphNode(baseType); + } + } + + /// + /// Builds a graph of all type definitions in the specified set of project contents. + /// + /// The resulting graph may be cyclic if there are cyclic type definitions. + public static Dictionary BuildTypeInheritanceGraph(IEnumerable projectContents) + { + if (projectContents == null) + throw new ArgumentNullException("projectContents"); + Dictionary dict = new Dictionary(); + using (var allContexts = new CompositeTypeResolveContext(projectContents).Synchronize()) { // lock all project contents + foreach (ITypeDefinition typeDef in allContexts.GetAllTypes()) { + dict.Add(typeDef, new TypeGraphNode(typeDef)); + } + foreach (IProjectContent pc in projectContents) { + using (var context = ParserService.GetTypeResolveContext(pc).Synchronize()) { + foreach (ITypeDefinition typeDef in pc.GetAllTypes()) { + TypeGraphNode typeNode = dict[typeDef]; + foreach (IType baseType in typeDef.GetBaseTypes(context)) { + ITypeDefinition baseTypeDef = baseType.GetDefinition(); + if (baseTypeDef != null) { + TypeGraphNode baseTypeNode; + if (dict.TryGetValue(baseTypeDef, out baseTypeNode)) { + typeNode.BaseTypes.Add(baseTypeNode); + baseTypeNode.DerivedTypes.Add(typeNode); + } + } + } + } + } + } + } + return dict; + } + #endregion } public class SymbolSearchArgs diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/GoToClassAction.cs b/src/Main/Base/Project/Src/Services/RefactoringService/GoToClassAction.cs index 94bd550387..646a8914b8 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/GoToClassAction.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/GoToClassAction.cs @@ -2,23 +2,23 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using ICSharpCode.NRefactory.TypeSystem; namespace ICSharpCode.SharpDevelop.Refactoring { - /* public class GoToClassAction : IContextAction { public string Title { get; private set; } - public IClass Class { get; private set; } + public ITypeDefinition Class { get; private set; } - public GoToClassAction(IClass c, IAmbience ambience) + public GoToClassAction(ITypeDefinition c, string title) { - if (ambience == null) - throw new ArgumentNullException("ambience"); if (c == null) throw new ArgumentNullException("c"); + if (title == null) + throw new ArgumentNullException("title"); this.Class = c; - this.Title = ambience.Convert(c); + this.Title = title; } public void Execute() @@ -26,5 +26,4 @@ namespace ICSharpCode.SharpDevelop.Refactoring NavigationService.NavigateTo(this.Class); } } - */ } diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/TypeGraphNode.cs b/src/Main/Base/Project/Src/Services/RefactoringService/TypeGraphNode.cs new file mode 100644 index 0000000000..5f1be8d6a6 --- /dev/null +++ b/src/Main/Base/Project/Src/Services/RefactoringService/TypeGraphNode.cs @@ -0,0 +1,58 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.SharpDevelop.Refactoring +{ + public sealed class TypeGraphNode + { + internal readonly ITypeDefinition typeDef; + readonly List baseTypes = new List(); + readonly List derivedTypes = new List(); + + public TypeGraphNode(ITypeDefinition typeDef) + { + this.typeDef = typeDef; + } + + public ITypeDefinition TypeDefinition { + get { return typeDef; } + } + + public IList DerivedTypes { + get { return derivedTypes; } + } + + public IList BaseTypes { + get { return baseTypes; } + } + + public ITreeNode ConvertToBaseTypeTree() + { + return TreeNode.FromGraph(this, n => n.baseTypes, n => n.typeDef); + } + + public ITreeNode ConvertToDerivedTypeTree() + { + return TreeNode.FromGraph(this, n => n.derivedTypes, n => n.typeDef); + } + } +} diff --git a/src/Main/Base/Project/Src/Util/TreeNode.cs b/src/Main/Base/Project/Src/Util/TreeNode.cs index d49b71f94d..c56c94b3c2 100644 --- a/src/Main/Base/Project/Src/Util/TreeNode.cs +++ b/src/Main/Base/Project/Src/Util/TreeNode.cs @@ -16,7 +16,7 @@ namespace ICSharpCode.SharpDevelop IEnumerable> Children { get; } } - public class TreeNode : ITreeNode + sealed class TreeNode : ITreeNode { public TreeNode(TContent content) { @@ -33,5 +33,24 @@ namespace ICSharpCode.SharpDevelop { return string.Format("[TreeNode {0}]", this.Content.ToString()); } + + public static TreeNode FromGraph(GraphNode rootNode, Func> children, Func content) + { + HashSet visited = new HashSet(); + return FromGraph(visited, rootNode, children, content); + } + + static TreeNode FromGraph(HashSet visited, GraphNode graphNode, Func> children, Func content) + { + TreeNode treeNode = new TreeNode(content(graphNode)); + List> childList = new List>(); + foreach (GraphNode graphChild in children(graphNode)) { + if (visited.Add(graphChild)) { // every graph node may appear only once in the tree + childList.Add(FromGraph(visited, graphChild, children, content)); + } + } + treeNode.Children = childList; + return treeNode; + } } }