如何防止其他线程更改字符串?

时间:2015-04-24 15:14:38

标签: c# .net string reference thread-safety

我创建了一个包含应用程序配置的类,以便多个线程可以访问其中的值。显然,我在属性和设置或读取这些值的方法中执行锁定。

public class Settings
{
    private readonly object m_BackupServersLocker = new object();
    private readonly List<Uri> m_BackupServers = new List<Uri>();

    private readonly object m_ExcludedFileExtensionsLocker = new object();
    private readonly List<string> m_ExcludedFileExtensions = new List<string>();

    /// <summary>
    /// The working threads use this property to get the addresses of the remote backup servers.
    /// </summary>
    public IEnumerable<Uri> RemoteBackupServers
    {
        get
        {
            lock (m_RemoteBackupServersLocker)
            {
                List<Uri> endpoints = new List<Uri>();
                foreach (var uri in m_RemoteBackupServers)
                {
                    string uriString = string.Copy(uri.ToString());
                    endpoints.Add(new Uri(uriString));
                }
                return endpoints;
            }
        }
    }

    /// <summary>
    /// This method is invoked by the thread which reads the configuration from file.
    /// </summary>
    /// <param name="uri"></param>
    public bool InsertRemoteBackupServer(Uri uri)
    {
        lock (m_RemoteBackupServersLocker)
        {
            if (uri == null) return false;
            return m_RemoteBackupServers.Add(uri);
        }
    }

    /// <summary>
    /// This method is invoked by the thread which reads the configuration from file.
    /// </summary>
    /// <param name="uri"></param>
    /// <returns></returns>
    public bool RemoveRemoteBackupServer(Uri uri)
    {
        lock (m_RemoteBackupServersLocker)
        {
            if (uri == null) return false;
            return m_RemoteBackupServers.Remove(uri);
        }
    }

    /// <summary>
    /// The working threads use the property to get the list of excluded extensions.
    /// The property is also invoked by the thread which reads the configuration from file, in order to update the exclusion list.
    /// </summary>
    public IEnumerable<string> ExcludedFileExtensions
    {
        get
        {
            lock (m_ExcludedFileExtensionsLocker)
            {
                List<string> temp = new List<string>();
                foreach (var extension in m_ExcludedFileExtensions)
                {
                    string extString = string.Copy(extension);
                    temp.Add(extString);
                }
                return temp;
            }
        }
        set
        {
            lock (m_ExcludedFileExtensionsLocker)
            {
                m_ExcludedFileExtensions.Clear();
                foreach (var extension in value)
                {
                    temp.Add(extension);
                }
                return temp;
            }
        }
    }
}

为了返回IEnumerable<Uri>IEnumerable<string>,我使用string方法执行了string.Copy的副本。但是真的有必要制作那份副本吗?我决定根据以下推理制作string的副本:如果属性只返回成员属性(即对该属性的引用),读者线程可以更改它们,所以我决定返回深度这些清单的副本。

但是,字符串是不可变的,所以不必通过复制每个字符串和每个字符串来复制这些列表吗?换句话说,如果我按如下方式更改上述示例中的ExcludedFileExtensions属性,那么读者线程是否可以更改string变量中的原始m_ExcludedFileExtensions

public IEnumerable<string> ExcludedFileExtensions
{
    get
    {
        lock (m_ExcludedFileExtensionsLocker)
        {
            return new List<string>(m_ExcludedFileExtensions);
        }
    }
}

2 个答案:

答案 0 :(得分:2)

查看ReadOnlyCollection<T>类型。

以下是MSDN文章:https://msdn.microsoft.com/en-us/library/ms132474.aspx

基本上,

return new ReadOnlyCollection<string>(m_ExcludedFileExtensions);

将在不执行深层复制的情况下提供列表的包装,但阻止任何人从外部修改基础列表。当然,字符串本身也是不可变的。

答案 1 :(得分:1)

您实施ExcludedFileExtensions方法正在做一些您可能不需要做的事情。

您只需要在get上返回列表内容的副本,如果

  1. 列表中的内容可能会因Settings课程中的操作(即您AddRemove项目)而改变,或者< / LI>
  2. 您希望阻止客户将其转换为List<string>,然后将其用于AddRemove元素。
  3. 否则你可以这样做:

    public IEnumerable<string> ExcludedFileExtensions
    {
        get
        {
            var current = m_ExcludedFileExtensions;
            return current;
            // If it could change/you want to prevent client casts:
            // return an immutable copy.
            // return current.ToArray();
        }
        set
        {
            // Personally I find a setter replacing the entire contents
            // rather odd, but then again I don't know your use case.
            var newList = value != null ? value.ToList() : new List<string>();
            m_ExcludedFileExtensions = newList; 
        }
    }
    

    您还必须将m_ExcludedFileExtensions的声明更改为:

    private volatile List<string> m_ExcludedFileExtensions = new List<string>();
    

    是的,字符串是不可变的,所以在返回时你永远不必克隆它们。