有没有办法只在C#中设置一次属性

Nic*_*ell 63 .net c#

我正在寻找一种方法来允许C#对象中的属性只设置一次.编写代码很容易,但我宁愿使用标准机制(如果存在).

public OneShot<int> SetOnceProperty { get; set; }

我想要发生的是,如果属性尚未设置,则可以设置属性,但如果之前已设置,则抛出异常.它应该像Nullable值一样运行,我可以检查它是否已设置.

Mar*_*ell 49

在.NET 4.0的TPL中直接支持这一点;

(编辑:上面的句子写的是预期System.Threading.WriteOnce<T>当时可用的"预览"位中存在的,但这似乎在TPL命中RTM/GA之前已经消失)

直到那时才自己做检查......从我记得的内容来看,并不多.

就像是:

public sealed class WriteOnce<T>
{
    private T value;
    private bool hasValue;
    public override string ToString()
    {
        return hasValue ? Convert.ToString(value) : "";
    }
    public T Value
    {
        get
        {
            if (!hasValue) throw new InvalidOperationException("Value not set");
            return value;
        }
        set
        {
            if (hasValue) throw new InvalidOperationException("Value already set");
            this.value = value;
            this.hasValue = true;
        }
    }
    public T ValueOrDefault { get { return value; } }

    public static implicit operator T(WriteOnce<T> value) { return value.Value; }
}
Run Code Online (Sandbox Code Playgroud)

然后使用,例如:

readonly WriteOnce<string> name = new WriteOnce<string>();
public WriteOnce<string> Name { get { return name; } }
Run Code Online (Sandbox Code Playgroud)

  • 你介意添加这个的直接支持版本(因为我们已经超过了.NET 4.0).我似乎无法找到有关此功能的任何信息. (28认同)
  • @MarcGravell TPL到底能做到什么?`TaskCompletionSource <T>`? (3认同)
  • 我非常忙于完善自己的答案,我没有注意到你写了(实际上)同样的事情.我不再那么特别了. (2认同)

Mic*_*ows 30

您可以自己滚动(请参阅答案的结尾,以获得更强大的线程安全实现并支持默认值).

public class SetOnce<T>
{
    private bool set;
    private T value;

    public T Value
    {
        get { return value; }
        set
        {
            if (set) throw new AlreadySetException(value);
            set = true;
            this.value = value;
        }
    }

    public static implicit operator T(SetOnce<T> toConvert)
    {
        return toConvert.value;
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以像这样使用它:

public class Foo
{
    private readonly SetOnce<int> toBeSetOnce = new SetOnce<int>();

    public int ToBeSetOnce
    {
        get { return toBeSetOnce; }
        set { toBeSetOnce.Value = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

下面更强大的实现

public class SetOnce<T>
{
    private readonly object syncLock = new object();
    private readonly bool throwIfNotSet;
    private readonly string valueName;
    private bool set;
    private T value;

    public SetOnce(string valueName)
    {
        this.valueName = valueName;
        throwIfGet = true;
    }

    public SetOnce(string valueName, T defaultValue)
    {
        this.valueName = valueName;
        value = defaultValue;
    }

    public T Value
    {
        get
        {
            lock (syncLock)
            {
                if (!set && throwIfNotSet) throw new ValueNotSetException(valueName);
                return value;
            }
        }
        set
        {
            lock (syncLock)
            {
                if (set) throw new AlreadySetException(valueName, value);
                set = true;
                this.value = value;
            }
        }
    }

    public static implicit operator T(SetOnce<T> toConvert)
    {
        return toConvert.value;
    }
}


public class NamedValueException : InvalidOperationException
{
    private readonly string valueName;

    public NamedValueException(string valueName, string messageFormat)
        : base(string.Format(messageFormat, valueName))
    {
        this.valueName = valueName;
    }

    public string ValueName
    {
        get { return valueName; }
    }
}

public class AlreadySetException : NamedValueException
{
    private const string MESSAGE = "The value \"{0}\" has already been set.";

    public AlreadySetException(string valueName)
        : base(valueName, MESSAGE)
    {
    }
}

public class ValueNotSetException : NamedValueException
{
    private const string MESSAGE = "The value \"{0}\" has not yet been set.";

    public ValueNotSetException(string valueName)
        : base(valueName, MESSAGE)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

  • throwIfGet(在第一个构造函数中)没有在任何地方定义 (2认同)
  • 应该锁定属性 (2认同)

Ton*_*Nam 19

C# 9 内置了此功能。它称为Init only setters

public DateTime RecordedAt { get; init; }
Run Code Online (Sandbox Code Playgroud)

  • 这实际上并不相同,因为它只允许在初始化时设置值并且不会创建异常,而是编译错误 (12认同)

Ant*_*lev 11

这可以通过摆弄旗帜来完成:

private OneShot<int> setOnce;
private bool setOnceSet;

public OneShot<int> SetOnce
{
    get { return setOnce; }
    set
    {
        if(setOnceSet)
            throw new InvalidOperationException();

        setOnce = value;
        setOnceSet = true;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是不好的,因为您可能会收到运行时错误.在编译时强制执行此行为要好得多:

public class Foo
{
    private readonly OneShot<int> setOnce;        

    public OneShot<int> SetOnce
    {
        get { return setOnce; }
    }

    public Foo() :
        this(null)
    {
    }

    public Foo(OneShot<int> setOnce)
    {
        this.setOnce = setOnce;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用任一构造函数.

  • 如果我尝试设置它两次,我希望出现运行时错误。否则我会创建一个构造函数来设置只读成员变量 (2认同)

Viz*_*izu 5

C# 中没有这样的功能(截至 3.5)。你必须自己编码。

  • 我不知道。如果某些东西只应该设置一次,那么它可能应该是构造函数参数。 (6认同)
  • 微软应该提供这样的功能。 (2认同)