If I have two DirectoryInfo objects, how can I compare them for semantic equality? For example, the following paths should all be considered equal to C:\temp:
C:\temp
C:\temp\
C:\temp\.
C:\temp\x\..\..\temp\.
The following may or may not be equal to C:\temp:
\temp if the current working directory is on drive C:\
temp if the current working directory is C:\
C:\temp.
C:\temp...\
If it's important to consider the current working directory, I can figure that out myself, so that's not that important. Trailing dots are stripped in windows, so those paths really should be equal - but they aren't stripped in unix, so under mono I'd expect other results.
Case sensitivity is optional. The paths may or may not exist, and the user may or may not have permissions to the path - I'd prefer a fast robust method that doesn't require any I/O (so no permission checking), but if there's something built-in I'd be happy with anything "good enough" too...
I realize that without I/O it's not possible to determine whether some intermediate storage layer happens to have mapped the same storage to the same file (and even with I/O, when things get messy enough it's likely impossible). However, it should be possible to at least positively identify paths that are equivalent, regardless of the underlying filesystem, i.e. paths that necessarily would resolve to the same file (if it exists) on all possible file-systems of a given type. The reason this is sometimes useful is (A) because I certainly want to check this first, before doing I/O, (B) I/O sometimes triggers problematic side-effects, and (C) various other software components sometimes mangle paths provided, and it's helpful to be able to compare in a way that's insensitive to most common transformations of equivalent paths, and finally (D) to prepare deployments it's useful to do some sanity checks beforehand, but those occur before the to-be-deployed-on system is even accessible.
GetFullPath seems to do the work, except for case difference (Path.GetFullPath("test") != Path.GetFullPath("TEST")) and trailing slash.
So, the following code should work fine:
String.Compare(
Path.GetFullPath(path1).TrimEnd('\\'),
Path.GetFullPath(path2).TrimEnd('\\'),
StringComparison.InvariantCultureIgnoreCase)
Or, if you want to start with DirectoryInfo:
String.Compare(
dirinfo1.FullName.TrimEnd('\\'),
dirinfo2.FullName.TrimEnd('\\'),
StringComparison.InvariantCultureIgnoreCase)
From this answer, this method can handle a few edge cases:
public static string NormalizePath(string path)
{
return Path.GetFullPath(new Uri(path).LocalPath)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
.ToUpperInvariant();
}
More details in the original answer. Call it like:
bool pathsEqual = NormalizePath(path1) == NormalizePath(path2);
Should work for both file and directory paths.
The question has been edited and clarified since it was originally asked and since this answer was originally posted. As the question currently stands, this answer below is not a correct answer. Essentially, the current question is asking for a purely textual path comparison, which is quite different from wanting to determine if two paths resolve to the same file system object. All the other answers, with the exception of Igor Korkhov's, are ultimately based on a textual comparison of two names.
If one actually wants to know when two paths resolve to the same file system object, you must do some IO. Trying to get two "normalized" names, that take in to account the myriad of possible ways of referencing the same file object, is next to impossible. There are issues such as: junctions, symbolic links, network file shares (referencing the same file object in different manners), etc. etc. In fact, every single answer above, with the exception of Igor Korkhov's, will absolutely give incorrect results in certain circumstances to the question "do these two paths reference the same file system object. (e.g. junctions, symbolic links, directory links, etc.)
The question specifically requested that the solution not require any I/O, but if you are going to deal with networked paths, you will absolutely need to do IO: there are cases where it is simply not possible to determine from any local path-string manipulation, whether two file references will reference the same physical file. (This can be easily understood as follows. Suppose a file server has a windows directory junction somewhere within a shared subtree. In this case, a file can be referenced either directly, or through the junction. But the junction resides on, and is resolved by, the file server, and so it is simply impossible for a client to determine, purely through local information, that the two referencing file names refer to the same physical file: the information is simply not available locally to the client. Thus one must absolutely do some minimal IO - e.g. open two file object handles - to determine if the references refer to the same physical file.)
The following solution does some IO, though very minimal, but correctly determines whether two file system references are semantically identical, i.e. reference the same file object. (if neither file specification refers to a valid file object, all bets are off):
public static bool AreDirsEqual(string dirName1, string dirName2, bool resolveJunctionaAndNetworkPaths = true)
{
if (string.IsNullOrEmpty(dirName1) || string.IsNullOrEmpty(dirName2))
return dirName1==dirName2;
dirName1 = NormalizePath(dirName1); //assume NormalizePath normalizes/fixes case and path separators to Path.DirectorySeparatorChar
dirName2 = NormalizePath(dirName2);
int i1 = dirName1.Length;
int i2 = dirName2.Length;
do
{
--i1; --i2;
if (i1 < 0 || i2 < 0)
return i1 < 0 && i2 < 0;
} while (dirName1[i1] == dirName2[i2]);//If you want to deal with international character sets, i.e. if NormalixePath does not fix case, this comparison must be tweaked
if( !resolveJunctionaAndNetworkPaths )
return false;
for(++i1, ++i2; i1 < dirName1.Length; ++i1, ++i2)
{
if (dirName1[i1] == Path.DirectorySeparatorChar)
{
dirName1 = dirName1.Substring(0, i1);
dirName2 = dirName1.Substring(0, i2);
break;
}
}
return AreFileSystemObjectsEqual(dirName1, dirName2);
}
public static bool AreFileSystemObjectsEqual(string dirName1, string dirName2)
{
//NOTE: we cannot lift the call to GetFileHandle out of this routine, because we _must_
// have both file handles open simultaneously in order for the objectFileInfo comparison
// to be guaranteed as valid.
using (SafeFileHandle directoryHandle1 = GetFileHandle(dirName1), directoryHandle2 = GetFileHandle(dirName2))
{
BY_HANDLE_FILE_INFORMATION? objectFileInfo1 = GetFileInfo(directoryHandle1);
BY_HANDLE_FILE_INFORMATION? objectFileInfo2 = GetFileInfo(directoryHandle2);
return objectFileInfo1 != null
&& objectFileInfo2 != null
&& (objectFileInfo1.Value.FileIndexHigh == objectFileInfo2.Value.FileIndexHigh)
&& (objectFileInfo1.Value.FileIndexLow == objectFileInfo2.Value.FileIndexLow)
&& (objectFileInfo1.Value.VolumeSerialNumber == objectFileInfo2.Value.VolumeSerialNumber);
}
}
static SafeFileHandle GetFileHandle(string dirName)
{
const int FILE_ACCESS_NEITHER = 0;
//const int FILE_SHARE_READ = 1;
//const int FILE_SHARE_WRITE = 2;
//const int FILE_SHARE_DELETE = 4;
const int FILE_SHARE_ANY = 7;//FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE
const int CREATION_DISPOSITION_OPEN_EXISTING = 3;
const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
return CreateFile(dirName, FILE_ACCESS_NEITHER, FILE_SHARE_ANY, System.IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, System.IntPtr.Zero);
}
static BY_HANDLE_FILE_INFORMATION? GetFileInfo(SafeFileHandle directoryHandle)
{
BY_HANDLE_FILE_INFORMATION objectFileInfo;
if ((directoryHandle == null) || (!GetFileInformationByHandle(directoryHandle.DangerousGetHandle(), out objectFileInfo)))
{
return null;
}
return objectFileInfo;
}
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
IntPtr SecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetFileInformationByHandle(IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[StructLayout(LayoutKind.Sequential)]
public struct BY_HANDLE_FILE_INFORMATION
{
public uint FileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
public uint VolumeSerialNumber;
public uint FileSizeHigh;
public uint FileSizeLow;
public uint NumberOfLinks;
public uint FileIndexHigh;
public uint FileIndexLow;
};
Note that in the above code I have included two lines like dirName1 = NormalizePath(dirName1); and have not specified what the function NormalizePath is. NormalizePath can be any path-normalization function - many have been provided in answers elsewhere in this question. Providing a reasonable NormalizePath function means that AreDirsEqual will give a reasonable answer even when the two input paths refer to non-existent file system objects, i.e. to paths that you simply want to compare on a string-level. ( Ishmaeel's comment above should be paid heed as well, and this code does not do that...)
(There may be subtle permissions issues with this code, if a user has only traversal permissions on some initial directories, I am not sure if the file system accesses required by AreFileSystemObjectsEqual are permitted. The parameter resolveJunctionaAndNetworkPaths at least allows the user to revert to pure textual comparison in this case...)
The idea for this came from a reply by Warren Stevens in a similar question I posted on SuperUser: https://superuser.com/a/881966/241981
There are some short comes to the implementation of paths in .NET. There are many complaints about it. Patrick Smacchia, the creator of NDepend, published an open source library that enables handling of common and complex path operations. If you do a lot of compare operations on paths in your application, this library might be useful to you.
It seems that P/Invoking GetFinalPathNameByHandle() would be the most reliable solution.
UPD: Oops, I didn't take into account your desire not to use any I/O
Microsoft has implemented similar methods, although they are not as useful as the answers above:
PathUtil.ArePathsEqual Method (which is just return string.Equals(path1, path2, StringComparison.OrdinalIgnoreCase);)
PathUtil.Normalize Method
PathUtil.NormalizePath Method (which is just return PathUtil.Normalize(path);)
System.IO.Path.GetFullPath(pathA).Equals(System.IO.Path.GetFullPath(PathB));
The "Name" properties are equal. Take:
DirectoryInfo dir1 = new DirectoryInfo("C:\\Scratch");
DirectoryInfo dir2 = new DirectoryInfo("C:\\Scratch\\");
DirectoryInfo dir3 = new DirectoryInfo("C:\\Scratch\\4760");
DirectoryInfo dir4 = new DirectoryInfo("C:\\Scratch\\4760\\..\\");
dir1.Name == dir2.Name and dir2.Name == dir4.Name ("Scratch" in this case. dir3 == "4760".) It's only the FullName properties that are different.
You might be able to do a recursive method to examine the Name properties of each parent given your two DirectoryInfo classes to ensure the complete path is the same.
EDIT: does this work for your situation? Create a Console Application and paste this over the entire Program.cs file. Provide two DirectoryInfo objects to the AreEquals() function and it will return True if they're the same directory. You might be able to tweak this AreEquals() method to be an extension method on DirectoryInfo if you like, so you could just do myDirectoryInfo.IsEquals(myOtherDirectoryInfo);
using System;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(AreEqual(
new DirectoryInfo("C:\\Scratch"),
new DirectoryInfo("C:\\Scratch\\")));
Console.WriteLine(AreEqual(
new DirectoryInfo("C:\\Windows\\Microsoft.NET\\Framework"),
new DirectoryInfo("C:\\Windows\\Microsoft.NET\\Framework\\v3.5\\1033\\..\\..")));
Console.WriteLine(AreEqual(
new DirectoryInfo("C:\\Scratch\\"),
new DirectoryInfo("C:\\Scratch\\4760\\..\\..")));
Console.WriteLine("Press ENTER to continue");
Console.ReadLine();
}
private static bool AreEqual(DirectoryInfo dir1, DirectoryInfo dir2)
{
DirectoryInfo parent1 = dir1;
DirectoryInfo parent2 = dir2;
/* Build a list of parents */
List<string> folder1Parents = new List<string>();
List<string> folder2Parents = new List<string>();
while (parent1 != null)
{
folder1Parents.Add(parent1.Name);
parent1 = parent1.Parent;
}
while (parent2 != null)
{
folder2Parents.Add(parent2.Name);
parent2 = parent2.Parent;
}
/* Now compare the lists */
if (folder1Parents.Count != folder2Parents.Count)
{
// Cannot be the same - different number of parents
return false;
}
bool equal = true;
for (int i = 0; i < folder1Parents.Count && i < folder2Parents.Count; i++)
{
equal &= folder1Parents[i] == folder2Parents[i];
}
return equal;
}
}
}
You can use Minimatch, a port of Node.js' minimatch.
var mm = new Minimatcher(searchPattern, new Options { AllowWindowsPaths = true });
if (mm.IsMatch(somePath))
{
// The path matches! Do some cool stuff!
}
var matchingPaths = mm.Filter(allPaths);
See why the AllowWindowsPaths = true option is necessary:
On Windows-style paths
Minimatch's syntax was designed for Linux-style paths (with forward slashes only). In particular, it uses the backslash as an escape character, so it cannot simply accept Windows-style paths. My C# version preserves this behavior.
To suppress this, and allow both backslashes and forward slashes as path separators (in patterns or input), set the AllowWindowsPaths option:
var mm = new Minimatcher(searchPattern, new Options { AllowWindowsPaths = true });
Passing this option will disable escape characters entirely.
Nuget: http://www.nuget.org/packages/Minimatch/
GitHub: https://github.com/SLaks/Minimatch
using System;
using System.Collections.Generic;
using System.Text;
namespace EventAnalysis.IComparerImplementation
{
public sealed class FSChangeElemComparerByPath : IComparer<FSChangeElem>
{
public int Compare(FSChangeElem firstPath, FSChangeElem secondPath)
{
return firstPath.strObjectPath == null ?
(secondPath.strObjectPath == null ? 0 : -1) :
(secondPath.strObjectPath == null ? 1 : ComparerWrap(firstPath.strObjectPath, secondPath.strObjectPath));
}
private int ComparerWrap(string stringA, string stringB)
{
int length = 0;
int start = 0;
List<string> valueA = new List<string>();
List<string> valueB = new List<string>();
ListInit(ref valueA, stringA);
ListInit(ref valueB, stringB);
if (valueA.Count != valueB.Count)
{
length = (valueA.Count > valueB.Count)
? valueA.Count : valueB.Count;
if (valueA.Count != length)
{
for (int i = 0; i < length - valueA.Count; i++)
{
valueA.Add(string.Empty);
}
}
else
{
for (int i = 0; i < length - valueB.Count; i++)
{
valueB.Add(string.Empty);
}
}
}
else
length = valueA.Count;
return RecursiveComparing(valueA, valueB, length, start);
}
private void ListInit(ref List<string> stringCollection, string stringToList)
{
foreach (string s in stringToList.Remove(0, 2).Split('\\'))
{
stringCollection.Add(s);
}
}
private int RecursiveComparing(List<string> valueA, List<string> valueB, int length, int start)
{
int result = 0;
if (start != length)
{
if (valueA[start] == valueB[start])
{
result = RecursiveComparing(valueA, valueB, length, ++start);
}
else
{
result = String.Compare(valueA[start], valueB[start]);
}
}
else
return 0;
return result;
}
}
}
I used recursion to solve this problem for myself.
public bool PathEquals(string Path1, string Path2)
{
FileInfo f1 = new FileInfo(Path1.Trim('\\','/','.'));
FileInfo f2 = new FileInfo(Path2.Trim('\\', '/','.'));
if(f1.Name.ToLower() == f2.Name.ToLower())
{
return DirectoryEquals(f1.Directory, f2.Directory);
}
else
{
return false;
}
}
public bool DirectoryEquals(DirectoryInfo d1, DirectoryInfo d2)
{
if(d1.Name.ToLower() == d2.Name.ToLower())
{
if((d1.Parent != null) && (d2.Parent != null))
{
return DirectoryEquals(d1.Parent, d2.Parent);
}
else
{
return true;//C:\Temp1\Temp2 equals \Temp1\Temp2
//return (d1.Parent == null) && (d2.Parent == null);//C:\Temp1\Temp2 does not equal \Temp1\Temp2
}
}
else
{
return false;
}
}
Note: new FileInfo(path) returns a valid FileInfo even if path is not a file (the name field is equal to the directory name)
Thank you, #Andy Shellam and #VladV and #Eamon Nerbonne
I found the other solution:
private static bool AreEqual(DirectoryInfo dir1, DirectoryInfo dir2)
{
return AreEqual(dir1.FullName, dir2.FullName);
}
private static bool AreEqual(string folderPath1, string folderPath2)
{
folderPath1 = Path.GetFullPath(folderPath1);
folderPath2 = Path.GetFullPath(folderPath2);
if (folderPath1.Length == folderPath2.Length)
{
return string.Equals(folderPath1, folderPath2/*, StringComparison.OrdinalIgnoreCase*/);
}
else if (folderPath1.Length == folderPath2.Length + 1 && IsEndWithAltDirectorySeparatorChar(folderPath1))
{
// folderPath1 = #"F:\temp\"
// folderPath2 = #"F:\temp"
return folderPath1.Contains(folderPath2 /*, StringComparison.OrdinalIgnoreCase*/);
}
else if (folderPath1.Length + 1 == folderPath2.Length && IsEndWithAltDirectorySeparatorChar(folderPath2))
{
// folderPath1 = #"F:\temp"
// folderPath2 = #"F:\temp\"
return folderPath2.Contains(folderPath1 /*, StringComparison.OrdinalIgnoreCase*/);
}
return false;
static bool IsEndWithAltDirectorySeparatorChar(string path)
{
var lastChar = path[path.Length - 1];
return lastChar == Path.DirectorySeparatorChar;
}
}
It can work well.
static void Main(string[] args)
{
Console.WriteLine(AreEqual(
new DirectoryInfo("C:\\Scratch"),
new DirectoryInfo("C:\\Scratch\\")));
Console.WriteLine(AreEqual(
new DirectoryInfo("C:\\Windows\\Microsoft.NET\\Framework"),
new DirectoryInfo("C:\\Windows\\Microsoft.NET\\Framework\\v3.5\\1033\\..\\..")));
Console.WriteLine(AreEqual(
new DirectoryInfo("C:\\Scratch\\"),
new DirectoryInfo("C:\\Scratch\\4760\\..\\..")));
Debug.WriteLine(AreEqual(#"C:/Temp", #"C:\Temp2")); // False
Debug.WriteLine(AreEqual(#"C:\Temp\", #"C:\Temp2"));// False
Debug.WriteLine(AreEqual(#"C:\Temp\", #"C:\Temp")); // True
Debug.WriteLine(AreEqual(#"C:\Temp/", #"C:\Temp")); // True
Debug.WriteLine(AreEqual(#"C:/Temp/", #"C:\Temp\"));// True
Console.WriteLine("Press ENTER to continue");
Console.ReadLine();
}
bool equals = myDirectoryInfo1.FullName == myDirectoryInfo2.FullName;
?
bool Equals(string path1, string path2)
{
return new Uri(path1) == new Uri(path2);
}
Uri constructor normalizes the path.
I want to get from this
"../lib/../data/myFile.xml"
to this
"../data/myFile.xml"
I guess I could do it by manipulating the string, searching for "../" and canceling them out with the preceding folders but I was looking for an already existing C# solution.
Tried instantiating an Uri from this string and going back toString(). Didn't help. It leaves the string unchanged.
You can always try to use:
Path.GetFullPath("../lib/../data/myFile.xml")
It behaves as you want with absolute paths but you might end up with strange behaviors with relative paths since it always bases itself from the current working directory. For instance:
Path.GetFullPath("/lib/../data/myFile.xml") // C:\data\myFile.xml
Path.GetFullPath("../lib/../data/myFile.xml") // C:\Program Files (x86)\data\myFile.xml
Sounds like you may either need to parse/rebuild the path yourself, or use some kind of well constructed regular expression to do this for you.
Taking the parse/rebuild route, you could do something like:
public static string NormalisePath(string path)
{
var components = path.Split(new Char[] {'/'});
var retval = new Stack<string>();
foreach (var bit in components)
{
if (bit == "..")
{
if (retval.Any())
{
var popped = retval.Pop();
if (popped == "..")
{
retval.Push(popped);
retval.Push(bit);
}
}
else
{
retval.Push(bit);
}
}
else
{
retval.Push(bit);
}
}
var final = retval.ToList();
final.Reverse();
return string.Join("/", final.ToArray());
}
(and yes, you'd probably want better variable names/commenting/etc.)
You can use a regular expression to do this:
public static string NormalisePath(string path)
{
return new Regex(#"\.{2}/.*/(?=\.\.)").Replace(path, "");
}
The code found in the PresentationCore.dll (.NET4 WPF) by ILSpy:
// MS.Internal.PresentationCore.BindUriHelper
internal static string UriToString(Uri uri)
{
if (uri == null)
{
throw new ArgumentNullException("uri");
}
return new StringBuilder(uri.GetComponents(uri.IsAbsoluteUri ? UriComponents.AbsoluteUri : UriComponents.SerializationInfoString, UriFormat.SafeUnescaped), 2083).ToString();
}
The return type of uri.GetComponents is string, why didn't the method just return the string value instead of wrapping it in a StringBuilder(string).ToString(); Is this by design? What would be the reason for doing this in a general sense? Would it reduce allocations or improve Garbage Collection or used for thread safety?
Only thing I can think of is that if the first parameter being passed into the stringBuilder is null, then the stringbuilder will return string.empty rather than null (see http://msdn.microsoft.com/en-us/library/zb91weab(v=vs.100).aspx)
A string is nullable though... so why bother?!
Just doing a check and returning an empty string yourself would be a lot more efficient than newing up a stringBuilder instance.
The second parameter is just a suggested size that the stringbuilder should be initialized to...
Comments on the OP's question are right, it appears to be overkill.
/// <SecurityNote>
/// Critical: Calls the native InternetGetCookieEx(). There is potential for information disclosure.
/// Safe: A WebPermission demand is made for the given URI.
/// </SecurityNote>
[SecurityCritical, SecurityTreatAsSafe]
[FriendAccessAllowed] // called by PF.Application.GetCookie()
[SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke",
Justification="It's okay now. Be careful on change.")]
internal static string GetCookie(Uri uri, bool throwIfNoCookie)
{
// Always demand in order to prevent any cross-domain information leak.
SecurityHelper.DemandWebPermission(uri);
UInt32 size = 0;
string uriString = BindUriHelper.UriToString(uri);
if (UnsafeNativeMethods.InternetGetCookieEx(uriString, null, null, ref size, 0, IntPtr.Zero))
{
Debug.Assert(size > 0);
size++;
System.Text.StringBuilder sb = new System.Text.StringBuilder((int)size);
// PresentationHost intercepts InternetGetCookieEx(). It will set the INTERNET_COOKIE_THIRD_PARTY
// flag if necessary.
if (UnsafeNativeMethods.InternetGetCookieEx(uriString, null, sb, ref size, 0, IntPtr.Zero))
{
return sb.ToString();
}
}
if (!throwIfNoCookie && Marshal.GetLastWin32Error() == NativeMethods.ERROR_NO_MORE_ITEMS)
return null;
throw new Win32Exception(/*uses last error code*/);
}
/// <SecurityNote>
/// Critical: Sets cookies via the native InternetSetCookieEx(); doesn't demand WebPermission for the given
/// URI. This creates danger of overwriting someone else's cookies.
/// The P3P header has to be from an authentic web response in order to be trusted at all.
/// </SecurityNote>
[SecurityCritical]
private static bool SetCookieUnsafe(Uri uri, string cookieData, string p3pHeader)
{
string uriString = BindUriHelper.UriToString(uri);
// PresentationHost intercepts InternetSetCookieEx(). It will set the INTERNET_COOKIE_THIRD_PARTY
// flag if necessary. (This doesn't look very elegant but is much simpler than having to make the
// 3rd party decision here as well or calling into the native code (from PresentationCore).)
uint res = UnsafeNativeMethods.InternetSetCookieEx(
uriString, null, cookieData, UnsafeNativeMethods.INTERNET_COOKIE_EVALUATE_P3P, p3pHeader);
if(res == 0)
throw new Win32Exception(/*uses last error code*/);
return res != UnsafeNativeMethods.COOKIE_STATE_REJECT;
}
private const int MAX_PATH_LENGTH = 2048 ;
private const int MAX_SCHEME_LENGTH = 32;
public const int MAX_URL_LENGTH = MAX_PATH_LENGTH + MAX_SCHEME_LENGTH + 3; /*=sizeof("://")*/
//
// Uri-toString does 3 things over the standard .toString()
//
// 1) We don't unescape special control characters. The default Uri.ToString()
// will unescape a character like ctrl-g, or ctrl-h so the actual char is emitted.
// However it's considered safer to emit the escaped version.
//
// 2) We truncate urls so that they are always <= MAX_URL_LENGTH
//
// This method should be called whenever you are taking a Uri
// and performing a p-invoke on it.
//
internal static string UriToString(Uri uri)
{
if (uri == null)
{
throw new ArgumentNullException("uri");
}
return new StringBuilder(
uri.GetComponents(
uri.IsAbsoluteUri ? UriComponents.AbsoluteUri : UriComponents.SerializationInfoString,
UriFormat.SafeUnescaped),
MAX_URL_LENGTH).ToString();
}
I often want to do this:
public void Foo(Bar arg)
{
throw new ArgumentException("Argument is incompatible with " + name(Foo));
}
Because if I change the name of Foo the IDE will refactor my error message too, what won't happen if I put the name of the method (or any other kind of member identifier) inside a string literal. The only way I know of implementing "name" is by using reflection, but I think the performance loss outweighs the mantainability gain and it won't cover all kinds of identifiers.
The value of the expression between parenthesis could be computed at compile time (like typeof) and optimized to become one string literal by changing the language specification. Do you think this is a worthy feature?
PS: The first example made it look like the question is related only to exceptions, but it is not. Think of every situation you may want to reference a type member identifier. You'll have to do it through a string literal, right?
Another example:
[RuntimeAcessibleDocumentation(Description="The class " + name(Baz) +
" does its job. See method " + name(DoItsJob) + " for more info.")]
public class Baz
{
[RuntimeAcessibleDocumentation(Description="This method will just pretend " +
"doing its job if the argument " + name(DoItsJob.Arguments.justPretend) +
" is true.")]
public void DoItsJob(bool justPretend)
{
if (justPretend)
Logger.log(name(justPretend) + "was true. Nothing done.");
}
}
UPDATE: this question was posted before C# 6, but may still be relevant for those who are using previous versions of the language. If you are using C# 6 check out the nameof operator, which does pretty much the same thing as the name operator in the examples above.
well, you could cheat and use something like:
public static string CallerName([CallerMemberName]string callerName = null)
{
return callerName;
}
and:
public void Foo(Bar arg)
{
throw new ArgumentException("Argument is incompatible with " + CallerName());
}
Here, all the work is done by the compiler (at compile-time), so if you rename the method it will immediately return the correct thing.
If you simply want the current method name: MethodBase.GetCurrentMethod().Name
If it's a type typeof(Foo).Name
If you want the name of a variable/parameter/field/property, with a little Expression tree
public static string GetFieldName<T>(Expression<Func<T>> exp)
{
var body = exp.Body as MemberExpression;
if (body == null)
{
throw new ArgumentException();
}
return body.Member.Name;
}
string str = "Hello World";
string variableName = GetFieldName(() => str);
For method names it's a little more tricky:
public static readonly MethodInfo CreateDelegate = typeof(Delegate).GetMethod("CreateDelegate", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(Type), typeof(object), typeof(MethodInfo) }, null);
public static string GetMethodName<T>(Expression<Func<T>> exp)
{
var body = exp.Body as UnaryExpression;
if (body == null || body.NodeType != ExpressionType.Convert)
{
throw new ArgumentException();
}
var call = body.Operand as MethodCallExpression;
if (call == null)
{
throw new ArgumentException();
}
if (call.Method != CreateDelegate)
{
throw new ArgumentException();
}
var method = call.Arguments[2] as ConstantExpression;
if (method == null)
{
throw new ArgumentException();
}
MethodInfo method2 = (MethodInfo)method.Value;
return method2.Name;
}
and when you call them you have to specify the type of a compatible delegate (Action, Action<...>, Func<...> ...)
string str5 = GetMethodName<Action>(() => Main);
string str6 = GetMethodName<Func<int>>(() => Method1);
string str7 = GetMethodName<Func<int, int>>(() => Method2);
or more simply, without using expressions :-)
public static string GetMethodName(Delegate del)
{
return del.Method.Name;
}
string str8 = GetMethodName((Action)Main);
string str9 = GetMethodName((Func<int>)Method1);
string str10 = GetMethodName((Func<int, int>)Method2);
As has been covered, using this approach for exceptions seems unnecessary due to the method name being in the call stack on the exception.
In relation to the other example in the question of logging the parameter value, it seems PostSharp would be a good candidate here, and probably would allow lots of new features of this kind that you're interested in.
Have a look at this page on PostSharp which came up when I searched for how to use PostSharp to log parameter values (which it covers). An excerpt taken from that page:
You can get a lot of useful information with an aspect, but there are three popular categories:
Code information: function name, class name, parameter values, etc. This can help you to reduce guessing in pinning down logic flaws or edge-case scenarios
Performance information: keep track of how much time a method is taking
Exceptions: catch select/all exceptions and log information about them
The original question is named "How to refer to an identifier without writing it into a string literal in C#?" This answer does not answer that question, instead, it answers the question "How to refer to an identifier by writing its name into a string literal using a preprocessor?"
Here is a very simple "proof of concept" C# preprocessor program:
using System;
using System.IO;
namespace StackOverflowPreprocessor
{
/// <summary>
/// This is a C# preprocessor program to demonstrate how you can use a preprocessor to modify the
/// C# source code in a program so it gets self-referential strings placed in it.
/// </summary>
public class PreprocessorProgram
{
/// <summary>
/// The Main() method is where it all starts, of course.
/// </summary>
/// <param name="args">must be one argument, the full name of the .csproj file</param>
/// <returns>0 = OK, 1 = error (error message has been written to console)</returns>
static int Main(string[] args)
{
try
{
// Check the argument
if (args.Length != 1)
{
DisplayError("There must be exactly one argument.");
return 1;
}
// Check the .csproj file exists
if (!File.Exists(args[0]))
{
DisplayError("File '" + args[0] + "' does not exist.");
return 1;
}
// Loop to process each C# source file in same folder as .csproj file. Alternative
// technique (used in my real preprocessor program) is to read the .csproj file as an
// XML document and process the <Compile> elements.
DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(args[0]));
foreach (FileInfo fileInfo in directoryInfo.GetFiles("*.cs"))
{
if (!ProcessOneFile(fileInfo.FullName))
return 1;
}
}
catch (Exception e)
{
DisplayError("Exception while processing .csproj file '" + args[0] + "'.", e);
return 1;
}
Console.WriteLine("Preprocessor normal completion.");
return 0; // All OK
}
/// <summary>
/// Method to do very simple preprocessing of a single C# source file. This is just "proof of
/// concept" - in my real preprocessor program I use regex and test for many different things
/// that I recognize and process in one way or another.
/// </summary>
private static bool ProcessOneFile(string fileName)
{
bool fileModified = false;
string lastMethodName = "*unknown*";
int i = -1, j = -1;
try
{
string[] sourceLines = File.ReadAllLines(fileName);
for (int lineNumber = 0; lineNumber < sourceLines.Length - 1; lineNumber++)
{
string sourceLine = sourceLines[lineNumber];
if (sourceLine.Trim() == "//?GrabMethodName")
{
string nextLine = sourceLines[++lineNumber];
j = nextLine.IndexOf('(');
if (j != -1)
i = nextLine.LastIndexOf(' ', j);
if (j != -1 && i != -1 && i < j)
lastMethodName = nextLine.Substring(i + 1, j - i - 1);
else
{
DisplayError("Unable to find method name in line " + (lineNumber + 1) +
" of file '" + fileName + "'.");
return false;
}
}
else if (sourceLine.Trim() == "//?DumpNameInStringAssignment")
{
string nextLine = sourceLines[++lineNumber];
i = nextLine.IndexOf('\"');
if (i != -1 && i != nextLine.Length - 1)
{
j = nextLine.LastIndexOf('\"');
if (i != j)
{
sourceLines[lineNumber] =
nextLine.Remove(i + 1) + lastMethodName + nextLine.Substring(j);
fileModified = true;
}
}
}
}
if (fileModified)
File.WriteAllLines(fileName, sourceLines);
}
catch (Exception e)
{
DisplayError("Exception while processing C# file '" + fileName + "'.", e);
return false;
}
return true;
}
/// <summary>
/// Method to display an error message on the console.
/// </summary>
private static void DisplayError(string errorText)
{
Console.WriteLine("Preprocessor: " + errorText);
}
/// <summary>
/// Method to display an error message on the console.
/// </summary>
internal static void DisplayError(string errorText, Exception exceptionObject)
{
Console.WriteLine("Preprocessor: " + errorText + " - " + exceptionObject.Message);
}
}
}
And here's a test file, based on the first half of the original question:
using System;
namespace StackOverflowDemo
{
public class DemoProgram
{
public class Bar
{}
static void Main(string[] args)
{}
//?GrabMethodName
public void Foo(Bar arg)
{
//?DumpNameInStringAssignment
string methodName = "??"; // Will be changed as necessary by preprocessor
throw new ArgumentException("Argument is incompatible with " + methodName);
}
}
}
To make the running of the preprocessor program a part of the build process you modify the .csproj file in two places. Insert this line in the first section:
<UseHostCompilerIfAvailable>false</UseHostCompilerIfAvailable>
(This is optional - see here https://stackoverflow.com/a/12163384/253938 for more information.)
And at the end of the .csproj file replace some lines that are commented-out with these lines:
<Target Name="BeforeBuild">
<Exec WorkingDirectory="D:\Merlinia\Trunk-Debug\Common\Build Tools\Merlinia Preprocessor\VS2012 projects\StackOverflowPreprocessor\bin" Command="StackOverflowPreprocessor.exe "$(MSBuildProjectFullPath)"" />
</Target>
Now when you recompile the test program the line that says
string methodName = "??"; // Will be changed as necessary by preprocessor
will be magically converted to say
string methodName = "Foo"; // Will be changed as necessary by preprocessor
OK?
Version 6 of C# has introduced the nameof operator which works like the name operator described in the examples of the question, but with some restrictions. Here are some examples and excerpts from the C# FAQ blog:
(if x == null) throw new ArgumentNullException(nameof(x));
You can put more elaborate dotted names in a nameof expression, but that’s just to tell the compiler where to look: only the final identifier will be used:
WriteLine(nameof(person.Address.ZipCode)); // prints "ZipCode"
Note: there are small design changes to nameof since the Preview was built. In the preview, dotted expressions like in the last example, where person is a variable in scope, are not allowed. Instead you have to dot in through the type.
Anybody has example for IVsProject.AddItem till now I have done following but did not understand how to use IVsProject.AddItem and msdn does not have any example.
private void MenuItemCallback(object sender, EventArgs e)
{
IVsSolution solutionService = GetService(typeof(SVsSolution)) as IVsSolution;
// get all projects in solution
IEnumHierarchies enumHierarchies = null;
Guid guid = Guid.Empty;
ErrorHandler.ThrowOnFailure(solutionService.GetProjectEnum(
(uint)__VSENUMPROJFLAGS.EPF_ALLINSOLUTION,
ref guid,
out enumHierarchies));
//Loop all projects found
if (enumHierarchies != null)
{
// Loop projects found
IVsHierarchy[] hierarchy = new IVsHierarchy[1];
uint fetched = 0;
while (enumHierarchies.Next(1, hierarchy, out fetched) == VSConstants.S_OK
&& fetched == 1)
{
Guid projectGuid;
ErrorHandler.ThrowOnFailure(hierarchy[0].GetGuidProperty(
VSConstants.VSITEMID_ROOT,
(int)__VSHPROPID.VSHPROPID_ProjectIDGuid,
out projectGuid));
IVsProject project = (IVsProject)hierarchy[0];
project.AddItem(VSConstants.VSITEMID_ROOT,
VSADDITEMOPERATION.VSADDITEMOP_OPENFILE,
1,
...)
}
}
}
following is also not working using DTE
private void MenuItemCallback(object sender, EventArgs e)
{
//GemFireWizardForm form = new GemFireWizardForm();
//form.ShowDialog();
//Get the solution service
IVsSolution solutionService = GetService(typeof(SVsSolution)) as IVsSolution;
// get all projects in solution
IEnumHierarchies enumHierarchies = null;
Guid guid = Guid.Empty;
ErrorHandler.ThrowOnFailure(solutionService.GetProjectEnum(
(uint)__VSENUMPROJFLAGS.EPF_ALLINSOLUTION,
ref guid,
out enumHierarchies));
//Loop all projects found
if (enumHierarchies != null)
{
// Loop projects found
IVsHierarchy[] hierarchy = new IVsHierarchy[1];
uint fetched = 0;
Guid projectGuid = Guid.Empty;
while (enumHierarchies.Next(1, hierarchy, out fetched) == VSConstants.S_OK
&& fetched == 1)
{
ErrorHandler.ThrowOnFailure(hierarchy[0].GetGuidProperty(
VSConstants.VSITEMID_ROOT,
(int)__VSHPROPID.VSHPROPID_ProjectIDGuid,
out projectGuid));
}
IVsHierarchy ppHierarchy = null;
IVsSolution solution = (IVsSolution)GetService(typeof(SVsSolution));
Int32 result = solution.GetProjectOfGuid(ref projectGuid, out ppHierarchy);
if (ppHierarchy != null && result == VSConstants.S_OK)
{
Object automationObject = null;
ppHierarchy.GetProperty(VSConstants.VSITEMID_ROOT,
(Int32)__VSHPROPID.VSHPROPID_ExtObject,
out automationObject);
if (automationObject != null)
{
EnvDTE.SolutionClass sc = automationObject as EnvDTE.SolutionClass;
EnvDTE.Projects projects = sc.Projects;
EnvDTE.Project project = projects.Item(1);
EnvDTE.ProjectItems pitems = project.ProjectItems;
pitems.AddFromFileCopy(#"e:\avinash\test.cpp");
}
}
}
}
sc is coming as null
I managed to add files to the root of the current project, using this piece of code :
string file = ... ; // Provide the absolute path of your file here
string[] files = new string[1] { file };
VSADDRESULT[] result = new VSADDRESULT[1];
proj.AddItem(
VSConstants.VSITEMID_ROOT,
VSADDITEMOPERATION.VSADDITEMOP_OPENFILE,
file,
1,
files,
IntPtr.Zero,
result);
Unfortunately, using IVsProject.AddItem it seems impossible to add filters to a project, or to add files under a filter. Indeed, MSDN documentation mentions about the parameter itemidloc :
itemidLoc
Type: System.UInt32
[in] Identifier of the container folder for the item being added. Should be VSITEMID_ROOT or other valid item identifier. See the enumeration VSITEMID. Note that this parameter is currently ignored because only adding items as children of a project node is supported. Projects that support the notion of folders will want to add the items relative to itemidLoc.
I have no idea why this does not work, but maybe you can try to use MPF or VSXtra(created by deep dive), they both help us easily manage HierachyNode.