Browse Source

Implemented "Find references".

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@149 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 21 years ago
parent
commit
9271e6d1fb
  1. 2
      src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
  2. 71
      src/Main/Base/Project/Src/Commands/ClassMemberMenuBuilder.cs
  3. 9
      src/Main/Base/Project/Src/Dom/Implementations/DefaultClass.cs
  4. 23
      src/Main/Base/Project/Src/Dom/Implementations/DefaultParameter.cs
  5. 2
      src/Main/Base/Project/Src/Dom/Implementations/LazyReturnType.cs
  6. 52
      src/Main/Base/Project/Src/Services/ParserService/CaseSensitiveProjectContent.cs
  7. 20
      src/Main/Base/Project/Src/Services/ParserService/ParserService.cs
  8. 127
      src/Main/Base/Project/Src/Services/ProjectService/ParseableFileContentEnumerator.cs
  9. 193
      src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs
  10. 63
      src/Main/Base/Project/Src/Services/RefactoringService/Reference.cs
  11. 12
      src/Main/Base/Test/ReflectionLayerTests.cs

2
src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj

@ -665,6 +665,8 @@ @@ -665,6 +665,8 @@
<Compile Include="Src\TextEditor\Bookmarks\ClassMemberBookmark.cs" />
<Compile Include="Src\Commands\ClassMemberMenuBuilder.cs" />
<Compile Include="Src\Services\RefactoringService\RefactoringService.cs" />
<Compile Include="Src\Services\RefactoringService\Reference.cs" />
<Compile Include="Src\Services\ProjectService\ParseableFileContentEnumerator.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Libraries\DockPanel_Src\WinFormsUI\WinFormsUI.csproj">

71
src/Main/Base/Project/Src/Commands/ClassMemberMenuBuilder.cs

@ -32,7 +32,6 @@ namespace ICSharpCode.SharpDevelop.Commands @@ -32,7 +32,6 @@ namespace ICSharpCode.SharpDevelop.Commands
ClassMemberBookmark bookmark = (ClassMemberBookmark)owner;
IMember member = bookmark.Member;
List<ToolStripItem> list = new List<ToolStripItem>();
cmd = new MenuCommand("&Rename", Rename);
cmd.Tag = member;
list.Add(cmd);
@ -55,12 +54,11 @@ namespace ICSharpCode.SharpDevelop.Commands @@ -55,12 +54,11 @@ namespace ICSharpCode.SharpDevelop.Commands
return list.ToArray();
}
#region GoToBase
void GoToBase(object sender, EventArgs e)
{
MenuCommand item = (MenuCommand)sender;
IMember member = (IMember)item.Tag;
IMember baseMember = FindBaseMember(member);
IMember baseMember = RefactoringService.FindBaseMember(member);
if (baseMember != null) {
ICompilationUnit cu = baseMember.DeclaringType.CompilationUnit;
if (cu != null) {
@ -77,49 +75,6 @@ namespace ICSharpCode.SharpDevelop.Commands @@ -77,49 +75,6 @@ namespace ICSharpCode.SharpDevelop.Commands
}
}
IMember FindSimilarMember(IClass type, IMember member)
{
if (member is IMethod) {
IMethod parentMethod = (IMethod)member;
foreach (IMethod m in type.Methods) {
if (string.Equals(parentMethod.Name, m.Name, StringComparison.InvariantCultureIgnoreCase)) {
if (m.IsStatic == parentMethod.IsStatic) {
if (DiffUtility.Compare(parentMethod.Parameters, m.Parameters) == 0) {
return m;
}
}
}
}
} else if (member is IProperty) {
IProperty parentMethod = (IProperty)member;
foreach (IProperty m in type.Properties) {
if (string.Equals(parentMethod.Name, m.Name, StringComparison.InvariantCultureIgnoreCase)) {
if (m.IsStatic == parentMethod.IsStatic) {
if (DiffUtility.Compare(parentMethod.Parameters, m.Parameters) == 0) {
return m;
}
}
}
}
}
return null;
}
IMember FindBaseMember(IMember member)
{
IClass parentClass = member.DeclaringType;
IClass baseClass = parentClass.BaseClass;
if (baseClass == null) return null;
foreach (IClass childClass in baseClass.ClassInheritanceTree) {
IMember m = FindSimilarMember(childClass, member);
if (m != null)
return m;
}
return null;
}
#endregion
void Rename(object sender, EventArgs e)
{
MenuCommand item = (MenuCommand)sender;
@ -136,7 +91,7 @@ namespace ICSharpCode.SharpDevelop.Commands @@ -136,7 +91,7 @@ namespace ICSharpCode.SharpDevelop.Commands
foreach (IClass derivedClass in derivedClasses) {
if (derivedClass.CompilationUnit == null) continue;
if (derivedClass.CompilationUnit.FileName == null) continue;
IMember m = FindSimilarMember(derivedClass, member);
IMember m = RefactoringService.FindSimilarMember(derivedClass, member);
if (m != null && m.Region != null) {
SearchResult res = new SimpleSearchResult(m.FullyQualifiedName, new Point(m.Region.BeginColumn - 1, m.Region.BeginLine - 1));
res.ProvidedDocumentInformation = GetDocumentInformation(derivedClass.CompilationUnit.FileName);
@ -146,6 +101,21 @@ namespace ICSharpCode.SharpDevelop.Commands @@ -146,6 +101,21 @@ namespace ICSharpCode.SharpDevelop.Commands
SearchReplaceInFilesManager.ShowSearchResults(results);
}
void FindReferences(object sender, EventArgs e)
{
MenuCommand item = (MenuCommand)sender;
IMember member = (IMember)item.Tag;
List<Reference> list = RefactoringService.FindReferences(member, null);
if (list == null) return;
List<SearchResult> results = new List<SearchResult>();
foreach (Reference r in list) {
SearchResult res = new SearchResult(r.Offset, r.Length);
res.ProvidedDocumentInformation = GetDocumentInformation(r.FileName);
results.Add(res);
}
SearchReplaceInFilesManager.ShowSearchResults(results);
}
ProvidedDocumentInformation GetDocumentInformation(string fileName)
{
foreach (IViewContent content in WorkbenchSingleton.Workbench.ViewContentCollection) {
@ -159,12 +129,5 @@ namespace ICSharpCode.SharpDevelop.Commands @@ -159,12 +129,5 @@ namespace ICSharpCode.SharpDevelop.Commands
ITextBufferStrategy strategy = StringTextBufferStrategy.CreateTextBufferFromFile(fileName);
return new ProvidedDocumentInformation(strategy, fileName, 0);
}
void FindReferences(object sender, EventArgs e)
{
MenuCommand item = (MenuCommand)sender;
IMember member = (IMember)item.Tag;
MessageService.ShowMessage("Not implemented.");
}
}
}

9
src/Main/Base/Project/Src/Dom/Implementations/DefaultClass.cs

@ -512,14 +512,13 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -512,14 +512,13 @@ namespace ICSharpCode.SharpDevelop.Dom
} else {
baseType = baseTypeStruct.parent.ProjectContent.SearchType(baseTypeStruct.name, baseTypeStruct.parent, 1, 1);
}
if (baseType != null) {
currentClass = baseType;
if (baseType == null || finishedClasses.Contains(baseType)) {
// prevent enumerating interfaces multiple times and endless loops when
// circular inheritance is found
if (finishedClasses.Contains(currentClass)) {
return MoveNext();
}
} else {
currentClass = baseType;
finishedClasses.Add(currentClass);
PutBaseClassesOnStack(currentClass);
return true;

23
src/Main/Base/Project/Src/Dom/Implementations/DefaultParameter.cs

@ -131,24 +131,23 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -131,24 +131,23 @@ namespace ICSharpCode.SharpDevelop.Dom
return r;
}
public virtual int CompareTo(IParameter value) {
public virtual int CompareTo(IParameter value)
{
if (value == null) return -1;
int cmp;
if (Name != null) {
cmp = Name.CompareTo(value.Name);
if (cmp != 0) {
return cmp;
}
}
if(0 != (cmp = (int)(Modifier - value.Modifier)))
if(0 != (cmp = ((int)Modifier - (int)value.Modifier)))
return cmp;
return DiffUtility.Compare(attributes, value.Attributes);
if (ReturnType.Equals(value.ReturnType))
return 0;
else
return -1;
}
int IComparable.CompareTo(object value) {
return CompareTo((IParameter)value);
int IComparable.CompareTo(object value)
{
return CompareTo(value as IParameter);
}
}
}

2
src/Main/Base/Project/Src/Dom/Implementations/LazyReturnType.cs

@ -130,7 +130,7 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -130,7 +130,7 @@ namespace ICSharpCode.SharpDevelop.Dom
else
return false;
}
if (declaringClass != rt.declaringClass) return false;
if (declaringClass.FullyQualifiedName != rt.declaringClass.FullyQualifiedName) return false;
return name == rt.name;
}

52
src/Main/Base/Project/Src/Services/ParserService/CaseSensitiveProjectContent.cs

@ -210,23 +210,6 @@ namespace ICSharpCode.Core @@ -210,23 +210,6 @@ namespace ICSharpCode.Core
new AddReferenceDelegate(AddReference).BeginInvoke(e.ReferenceProjectItem, null, null);
}
delegate string GetParseableContentDelegate(string fileName);
Encoding getParseableContentEncoding;
string GetParseableFileContent(string fileName)
{
// Loading the source files is done asynchronously:
// While one file is parsed, the next is already loaded from disk.
string res = project.GetParseableFileContent(fileName);
if (res != null)
return res;
// load file
using (StreamReader r = new StreamReader(fileName, getParseableContentEncoding)) {
return r.ReadToEnd();
}
}
internal int GetInitializationWorkAmount()
{
return project.Items.Count;
@ -235,42 +218,23 @@ namespace ICSharpCode.Core @@ -235,42 +218,23 @@ namespace ICSharpCode.Core
internal void Initialize2()
{
if (!initializing) return;
ProjectItem[] arr = project.Items.ToArray();
int progressStart = StatusBarService.ProgressMonitor.WorkDone;
ParseableFileContentEnumerator enumerator = new ParseableFileContentEnumerator(project);
try {
Properties textEditorProperties = ((Properties)PropertyService.Get("ICSharpCode.TextEditor.Document.Document.DefaultDocumentAggregatorProperties", new Properties()));
getParseableContentEncoding = Encoding.GetEncoding(textEditorProperties.Get("Encoding", 1252));
textEditorProperties = null;
StatusBarService.ProgressMonitor.TaskName = "Parsing " + project.Name + "...";
GetParseableContentDelegate pcd = new GetParseableContentDelegate(GetParseableFileContent);
ProjectItem item;
ProjectItem nextItem = arr[0];
IAsyncResult res = null;
for (int i = 0; i < arr.Length; ++i) {
item = nextItem;
nextItem = (i < arr.Length - 1) ? arr[i + 1] : null;
while (enumerator.MoveNext()) {
int i = enumerator.Index;
if ((i % 5) == 2)
StatusBarService.ProgressMonitor.WorkDone = progressStart + i;
if (item.ItemType == ItemType.Compile) {
string fileName = item.FileName;
string fileContent;
if (res != null)
fileContent = pcd.EndInvoke(res);
else
fileContent = GetParseableFileContent(fileName);
if (nextItem != null && nextItem.ItemType == ItemType.Compile)
res = pcd.BeginInvoke(nextItem.FileName, null, null);
else
res = null;
ParserService.ParseFile(this, fileName, fileContent, true, false);
}
ParserService.ParseFile(this, enumerator.CurrentFileName, enumerator.CurrentFileContent, true, false);
if (!initializing) return;
}
} finally {
initializing = false;
StatusBarService.ProgressMonitor.WorkDone = progressStart + arr.Length;
getParseableContentEncoding = null;
StatusBarService.ProgressMonitor.WorkDone = progressStart + enumerator.ItemCount;
enumerator.Dispose();
}
}

20
src/Main/Base/Project/Src/Services/ParserService/ParserService.cs

@ -47,12 +47,6 @@ namespace ICSharpCode.Core @@ -47,12 +47,6 @@ namespace ICSharpCode.Core
}
}
public static IEnumerable<IProjectContent> AllProjectContents {
get {
return projectContents.Values;
}
}
static IProjectContent forcedContent;
/// <summary>
/// Used for unit tests ONLY!!
@ -62,6 +56,14 @@ namespace ICSharpCode.Core @@ -62,6 +56,14 @@ namespace ICSharpCode.Core
forcedContent = content;
}
public static IEnumerable<IProjectContent> AllProjectContents {
get {
return projectContents.Values;
}
}
static ParserService()
{
try {
@ -101,6 +103,12 @@ namespace ICSharpCode.Core @@ -101,6 +103,12 @@ namespace ICSharpCode.Core
loadSolutionProjectsThread.Start();
}
public static bool LoadSolutionProjectsThreadRunning {
get {
return loadSolutionProjectsThread != null;
}
}
static void LoadSolutionProjects()
{
try {

127
src/Main/Base/Project/Src/Services/ProjectService/ParseableFileContentEnumerator.cs

@ -0,0 +1,127 @@ @@ -0,0 +1,127 @@
/*
* Created by SharpDevelop.
* User: Daniel Grunwald
* Date: 18.05.2005
* Time: 19:08
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ICSharpCode.Core;
namespace ICSharpCode.SharpDevelop.Project
{
/// <summary>
/// Description of ParseableFileContentEnumerator.
/// </summary>
public class ParseableFileContentEnumerator : IEnumerator<KeyValuePair<string, string>>
{
void IEnumerator.Reset() {
throw new NotSupportedException();
}
KeyValuePair<string, string> current;
object IEnumerator.Current {
get {
return current;
}
}
public KeyValuePair<string, string> Current {
get {
return current;
}
}
public string CurrentFileName {
get {
return current.Key;
}
}
public string CurrentFileContent {
get {
return current.Value;
}
}
public void Dispose()
{
}
ProjectItem[] projectItems;
public ParseableFileContentEnumerator(IProject project) : this(project.Items.ToArray()) { }
public ParseableFileContentEnumerator(ProjectItem[] projectItems)
{
this.projectItems = projectItems;
Properties textEditorProperties = ((Properties)PropertyService.Get("ICSharpCode.TextEditor.Document.Document.DefaultDocumentAggregatorProperties", new Properties()));
getParseableContentEncoding = Encoding.GetEncoding(textEditorProperties.Get("Encoding", 1252));
pcd = new GetParseableContentDelegate(GetParseableFileContent);
if (projectItems.Length > 0) {
nextItem = projectItems[0];
}
}
delegate string GetParseableContentDelegate(IProject project, string fileName);
GetParseableContentDelegate pcd;
Encoding getParseableContentEncoding;
string GetParseableFileContent(IProject project, string fileName)
{
// Loading the source files is done asynchronously:
// While one file is parsed, the next is already loaded from disk.
string res = project.GetParseableFileContent(fileName);
if (res != null)
return res;
// load file
using (StreamReader r = new StreamReader(fileName, getParseableContentEncoding)) {
return r.ReadToEnd();
}
}
ProjectItem nextItem;
IAsyncResult res;
int index = 0;
public int ItemCount {
get {
return projectItems.Length;
}
}
public int Index {
get {
return index;
}
}
public bool MoveNext()
{
ProjectItem item = nextItem;
nextItem = (++index < projectItems.Length) ? projectItems[index] : null;
if (item == null) return false;
if (item.ItemType != ItemType.Compile)
return MoveNext();
string fileName = item.FileName;
string fileContent;
if (res != null)
fileContent = pcd.EndInvoke(res);
else
fileContent = GetParseableFileContent(item.Project, fileName);
if (nextItem != null && nextItem.ItemType == ItemType.Compile)
res = pcd.BeginInvoke(nextItem.Project, nextItem.FileName, null, null);
else
res = null;
current = new KeyValuePair<string, string>(fileName, fileContent);
return true;
}
}
}

193
src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs

@ -7,7 +7,11 @@ @@ -7,7 +7,11 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Project;
namespace ICSharpCode.Core
{
@ -42,5 +46,194 @@ namespace ICSharpCode.Core @@ -42,5 +46,194 @@ namespace ICSharpCode.Core
return list;
}
#endregion
#region FindReferences
/// <summary>
/// Find all references to the specified member.
/// </summary>
public static List<Reference> FindReferences(IMember member, IProgressMonitor progressMonitor)
{
if (ParserService.LoadSolutionProjectsThreadRunning) {
MessageService.ShowMessage("Find references cannot be executed until all files have been parsed.");
return null;
}
IClass ownerClass = member.DeclaringType;
List<ProjectItem> files = GetPossibleFiles(ownerClass, member);
ParseableFileContentEnumerator enumerator = new ParseableFileContentEnumerator(files.ToArray());
List<Reference> references = new List<Reference>();
try {
if (progressMonitor != null) {
progressMonitor.BeginTask("Finding references...", files.Count);
}
while (enumerator.MoveNext()) {
if (progressMonitor != null) {
progressMonitor.WorkDone = enumerator.Index;
}
AddReferences(references, ownerClass, member, enumerator.CurrentFileName, enumerator.CurrentFileContent);
}
} finally {
if (progressMonitor != null) {
progressMonitor.Done();
}
enumerator.Dispose();
}
return references;
}
static void AddReferences(List<Reference> list, IClass parentClass, IMember member, string fileName, string fileContent)
{
string lowerFileContent = fileContent.ToLower();
if (lowerFileContent.IndexOf(parentClass.Name.ToLower()) < 0) return;
string lowerMemberName;
if (member is IMethod && ((IMethod)member).IsConstructor)
lowerMemberName = parentClass.Name.ToLower();
else
lowerMemberName = member.Name.ToLower();
//Console.WriteLine(fileName + " / " + lowerMemberName);
int pos = -1;
IExpressionFinder expressionFinder = null;
while ((pos = lowerFileContent.IndexOf(lowerMemberName, pos + 1)) >= 0) {
if (expressionFinder == null) {
expressionFinder = ParserService.GetExpressionFinder(fileName);
}
string expr = expressionFinder.FindFullExpression(fileContent, pos + 1);
if (expr != null) {
Point position = GetPosition(fileContent, pos);
ResolveResult rr = ParserService.Resolve(expr, position.Y, position.X, fileName, fileContent);
if (IsReferenceToMember(member, rr)) {
list.Add(new Reference(fileName, pos, lowerMemberName.Length, expr, rr));
}
}
}
}
static Point GetPosition(string fileContent, int pos)
{
int line = 1;
int column = 0;
for (int i = 0; i < pos; ++i) {
if (fileContent[i] == '\n') {
++line;
column = 0;
} else {
++column;
}
}
return new Point(column, line);
}
static List<ProjectItem> GetPossibleFiles(IClass ownerClass, IMember member)
{
// TODO: Optimize when member is not public
List<ProjectItem> resultList = new List<ProjectItem>();
foreach (IProject p in ProjectService.OpenSolution.Projects) {
IProjectContent pc = ParserService.GetProjectContent(p);
if (pc == null) continue;
if (pc != ownerClass.ProjectContent && !pc.HasReferenceTo(ownerClass.ProjectContent)) {
// unreferences project contents cannot reference the class
continue;
}
foreach (ProjectItem item in p.Items) {
if (item.ItemType == ItemType.Compile) {
resultList.Add(item);
}
}
}
return resultList;
}
#endregion
public static bool IsReferenceToMember(IMember member, ResolveResult rr)
{
MemberResolveResult mrr = rr as MemberResolveResult;
if (mrr != null)
return IsSimilarMember(mrr.ResolvedMember, member);
else
return false;
}
/// <summary>
/// Gets if member1 is the same as member2 or if member1 overrides member2.
/// </summary>
public static bool IsSimilarMember(IMember member1, IMember member2)
{
do {
if (IsSimilarMemberInternal(member1, member2))
return true;
} while ((member1 = FindBaseMember(member1)) != null);
return false;
}
static bool IsSimilarMemberInternal(IMember member1, IMember member2)
{
if (member1 == member2)
return true;
if (member1 == null || member2 == null)
return false;
if (member1.FullyQualifiedName != member2.FullyQualifiedName)
return false;
if (member1.IsStatic != member2.IsStatic)
return false;
if (member1 is IMethod) {
if (member2 is IMethod) {
if (DiffUtility.Compare(((IMethod)member1).Parameters, ((IMethod)member2).Parameters) != 0)
return false;
} else {
return false;
}
}
if (member1 is IProperty) {
if (member2 is IProperty) {
if (DiffUtility.Compare(((IProperty)member1).Parameters, ((IProperty)member2).Parameters) != 0)
return false;
} else {
return false;
}
}
return true;
}
public static IMember FindSimilarMember(IClass type, IMember member)
{
if (member is IMethod) {
IMethod parentMethod = (IMethod)member;
foreach (IMethod m in type.Methods) {
if (string.Equals(parentMethod.Name, m.Name, StringComparison.InvariantCultureIgnoreCase)) {
if (m.IsStatic == parentMethod.IsStatic) {
if (DiffUtility.Compare(parentMethod.Parameters, m.Parameters) == 0) {
return m;
}
}
}
}
} else if (member is IProperty) {
IProperty parentMethod = (IProperty)member;
foreach (IProperty m in type.Properties) {
if (string.Equals(parentMethod.Name, m.Name, StringComparison.InvariantCultureIgnoreCase)) {
if (m.IsStatic == parentMethod.IsStatic) {
if (DiffUtility.Compare(parentMethod.Parameters, m.Parameters) == 0) {
return m;
}
}
}
}
}
return null;
}
public static IMember FindBaseMember(IMember member)
{
IClass parentClass = member.DeclaringType;
IClass baseClass = parentClass.BaseClass;
if (baseClass == null) return null;
foreach (IClass childClass in baseClass.ClassInheritanceTree) {
IMember m = FindSimilarMember(childClass, member);
if (m != null)
return m;
}
return null;
}
}
}

63
src/Main/Base/Project/Src/Services/RefactoringService/Reference.cs

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version value="$version"/>
// </file>
using System;
using System.Collections.Generic;
using ICSharpCode.SharpDevelop.Dom;
namespace ICSharpCode.Core
{
/// <summary>
/// A reference to a class or class member.
/// </summary>
public class Reference
{
string fileName;
int offset, length;
string expression;
ResolveResult resolveResult;
public Reference(string fileName, int offset, int length, string expression, ResolveResult resolveResult)
{
this.fileName = fileName;
this.offset = offset;
this.length = length;
this.expression = expression;
this.resolveResult = resolveResult;
}
public string FileName {
get {
return fileName;
}
}
public int Offset {
get {
return offset;
}
}
public int Length {
get {
return length;
}
}
public string Expression {
get {
return expression;
}
}
public ResolveResult ResolveResult {
get {
return resolveResult;
}
}
}
}

12
src/Main/Base/Test/ReflectionLayerTests.cs

@ -42,5 +42,17 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -42,5 +42,17 @@ namespace ICSharpCode.SharpDevelop.Tests
Assert.AreEqual("System.Runtime.InteropServices._Exception", subClasses[3].FullyQualifiedName);
Assert.AreEqual("System.Object", subClasses[4].FullyQualifiedName);
}
[Test]
public void ParameterComparisonTest()
{
DefaultParameter p1 = new DefaultParameter("a", pc.GetClass("System.String").DefaultReturnType, null);
DefaultParameter p2 = new DefaultParameter("b", new GetClassReturnType(pc, "System.String"), null);
List<IParameter> a1 = new List<IParameter>();
List<IParameter> a2 = new List<IParameter>();
a1.Add(p1);
a2.Add(p2);
Assert.AreEqual(0, DiffUtility.Compare(a1, a2));
}
}
}

Loading…
Cancel
Save