C#中获取文件属性

在Explorer中,选择“详细”视图,可以看到很多文件属性,如图片的分辨率,MP3的艺术家、比特率,视频文件的分辨率等等等等;这些数据,有时候在C#中可能很难获取,比如图片的分辨率,需要用Image.FromFile得到Image类,然后才能得到分辨率数据,但是把图片加载到内存中,一是会加大内存开销,二是会更耗时。所以,今天我们就来说说怎么在C#中获取到文件的详细属性。

添加引用

在你的项目中添加对C:\Windows\System32\shell32.dll的引用,我们在前面一期《C#中使用SHFileOperation调用Windows的复制文件对话框》中也用到了这个动态链接库,不过当时是使用映射的方式,如今我们将其引用,直接使用。

using Shell32;

代码实现

        /// <summary>

        /// 获取文件属性字典

        /// </summary>

        /// <param name="filePath">文件路径</param>

        /// <returns>属性字典</returns>

        public static Dictionary<string, string> GetProperties(string filePath)

        {

            if (!File.Exists(filePath))

            {

                throw new FileNotFoundException("指定的文件不存在。", filePath);

            }

            //初始化Shell接口 

            Shell32.Shell shell = new Shell32.ShellClass();

            //获取文件所在父目录对象 

            Folder folder = shell.NameSpace(Path.GetDirectoryName(filePath));

            //获取文件对应的FolderItem对象 

            FolderItem item = folder.ParseName(Path.GetFileName(filePath));

            //字典存放属性名和属性值的键值关系对 

            Dictionary<string, string> Properties = new Dictionary<string, string>();

            int i = 0;

            while (true)

            {

                //获取属性名称 

                string key = folder.GetDetailsOf(null, i);

                if (string.IsNullOrEmpty(key))

                {

                    //当无属性可取时,退出循环 

                    break;

                }

                //获取属性值 

                string value = folder.GetDetailsOf(item, i);

                //保存属性 

                Properties.Add(key, value);

                i++;

            }

            return Properties;

        }
GetProperties

这个方法返回所有属性值,在我的Win7 Pro 64bit 上,返回了287个属性!可以想象,信息是很丰富的,但是速度也是够慢的。

可以看到,上面代码用了一个循环,获取属性名和属性值时都是通过i来索引的。那么,我们是不是就能不通过循环,而直接用下标来获取想要的属性呢?代码如下:

        /// <summary>

        /// 获取指定文件指定下标的属性值

        /// </summary>

        /// <param name="filePath">文件路径</param>

        /// <param name="index">属性下标</param>

        /// <returns>属性值</returns>

        public static string GetPropertyByIndex(string filePath, int index)

        {

            if (!File.Exists(filePath))

            {

                throw new FileNotFoundException("指定的文件不存在。", filePath);

            }

            //初始化Shell接口 

            Shell32.Shell shell = new Shell32.ShellClass();

            //获取文件所在父目录对象 

            Folder folder = shell.NameSpace(Path.GetDirectoryName(filePath));

            //获取文件对应的FolderItem对象 

            FolderItem item = folder.ParseName(Path.GetFileName(filePath));

            string value = null;

 

            //获取属性名称 

            string key = folder.GetDetailsOf(null, index);

            if (false == string.IsNullOrEmpty(key))

            {

                //获取属性值 

                value = folder.GetDetailsOf(item, index);

            }

            return value;

        }
GetPropertyByIndex

在我的系统环境上,分辨率“尺寸”下标是31,那么我只需要GetPropertyByIndex(@“D:\test.jpg”,31)就可以获取到分辨率信息了。但是特别需要注意,“尺寸”属性的下标,在不同的Windows版本(XP,Vista,Win7,Win2003等)不一定是一样的。

ok,我们还注意到每个属性都有对应的一个“属性名”,那么,我们能不能通过属性名来获取属性值呢,这样会比使用下标保险多了吧。代码如下:

        /// <summary>

        /// 获取指定文件指定属性名的值

        /// </summary>

        /// <param name="filePath">文件路径</param>

        /// <param name="propertyName">属性名</param>

        /// <returns>属性值</returns>

        public static string GetProperty(string filePath, string propertyName)

        {

            if (!File.Exists(filePath))

            {

                throw new FileNotFoundException("指定的文件不存在。", filePath);

            }

            //初始化Shell接口 

            Shell32.Shell shell = new Shell32.ShellClass();

            //获取文件所在父目录对象 

            Folder folder = shell.NameSpace(Path.GetDirectoryName(filePath));

            //获取文件对应的FolderItem对象 

            FolderItem item = folder.ParseName(Path.GetFileName(filePath));

            string value = null;

            int i = 0;

            while (true)

            {

                //获取属性名称 

                string key = folder.GetDetailsOf(null, i);

                if (string.IsNullOrEmpty(key))

                {

                    //当无属性可取时,退出循环 

                    break;

                }

                if (true == string.Equals(key, propertyName, StringComparison.CurrentCultureIgnoreCase))

                {

                    //获取属性值 

                    value = folder.GetDetailsOf(item, i);

                    break;

                }

 

                i++;

            }

            return value;

        }
GetProperty

这个方法是我一开始写的,通过在while里面加上属性名的判断,直到找到对应的属性名,则返回相应的属性值。

不过这个方法还是不够简洁,“尺寸”属性在31,意味着每一次都需要循环31次才能拿到我要的值,如果我要获取的属性名下标为287(参看上面),那么次数将更多,于是,我又对代码做了一些优化:

        /// <summary>

        /// 存储属性名与其下标(key值均为小写)

        /// </summary>

        private static Dictionary<string, int> _propertyIndex = null;

 

        /// <summary>

        /// 获取指定文件指定属性名的值

        /// </summary>

        /// <param name="filePath">文件路径</param>

        /// <param name="propertyName">属性名</param>

        /// <returns>属性值</returns>

        public static string GetPropertyEx(string filePath, string propertyName)

        {

            if (_propertyIndex == null)

            {

                InitPropertyIndex();

            }

            //转换为小写

            string propertyNameLow = propertyName.ToLower();

            if (_propertyIndex.ContainsKey(propertyNameLow))

            {

                int index = _propertyIndex[propertyNameLow];

                return GetPropertyByIndex(filePath, index);

            }

            return null;

        }

 

        /// <summary>

        /// 初始化属性名的下标

        /// </summary>

        private static void InitPropertyIndex()

        {

            Dictionary<string, int> propertyIndex = new Dictionary<string, int>();

            //获取本代码所在的文件作为临时文件,用于获取属性列表

            string tempFile = System.Reflection.Assembly.GetExecutingAssembly().FullName;

            Dictionary<string, string> allProperty = GetProperties(tempFile);

            if (allProperty != null)

            {

                int index = 0;

                foreach (var item in allProperty.Keys)

                {

                    //属性名统一转换为小写,用于忽略大小写

                    _propertyIndex.Add(item.ToLower(), index);

                    index++;

                }

            }

            _propertyIndex = propertyIndex;

        }
GetPropertyEx

_propertyIndex用于存储属性名与其下标,用Dictionary是因为_propertyIndex[key]的时间复杂度是O(1)。然后在GetPropertyEx方法中找到属性名对应的下标,直接返回该下标的属性值。InitPropertyIndex方法只会被调用一次。

好了,我们现在通过属性名来获取属性值,在不同系统之间应该不会有问题了吧?

不一定,原因你肯定也想到了,如果是在一个英文windows上,它的属性名里面不会有“尺寸”,对应的应该是“Resolution”之类的(我没有英文版系统,所以只是猜测),也不会有“名称”属性,而是“Name”;

总结一下,

方法名

适用

不适用

GetPropertyByIndex

不同语言的系统

不同版本的系统

GetPropertyEx

不同版本的系统

不同语言的系统

所以,根据你的程序可能的运行环境,选择适合你的方法;

再思考:要能在不同语言不同版本的系统将通用,该怎么办?

我目前想到的:建立同一个属性名在不同语言间的对应关系,如”尺寸”对应” Resolution”,然后,在代码里获取到系统语言,将属性名“翻译”成该语言,即可通过翻译后的属性名找到对应的属性值了。欢迎有实现了本方法或者更好方法的同学一起来讨论。

posted @ 2013-05-14 13:07  季风哥  阅读(4555)  评论(0编辑  收藏  举报