编写具有可编辑属性且在调用方法后变为只读的类的最佳方法是什么?

问题描述 投票:0回答:2

我已经按照下面描述的方式解决了我的问题,但我想找到最好的方法。

Typescript 具有通用类型 Readonly 解决了这个问题。

感谢您的建议 我使用了以下代码(.net6):

public interface IReadOnlyAbility
{
    bool IsReadOnly { get; }
    void ReadOnly();
    public T CheckForSetValue<T>(T PropertyValue)
    {
        if (IsReadOnly)
            throw new ReadOnlyException();
        return PropertyValue;
    }
}

public interface IUser : IReadOnlyAbility
{
    string UserName { get; set; }
    string Password { get; set; }
}

public class User : IUser
{
    private string userName = string.Empty;
    private string password = string.Empty;
    public bool IsReadOnly { get; private set; }
    public string UserName
    {
        get => userName;
        set => userName = CheckForSetValue(value);
    }
    public string Password
    {
        get => password;
        set => password = CheckForSetValue(value);
    }
    protected T CheckForSetValue<T>(T Value) => ((IReadOnlyAbility)this).CheckForSetValue(Value);
    public void ReadOnly() => IsReadOnly = true;
}

然后我添加了依赖注入>>>

Services.AddTransient<IUser, User>();

现在,我用了它:

var user = Services.GetService<IUser>();
user.UserName = "UserName";
user.Password = "Password";
user.ReadOnly();

c# oop design-patterns code-cleanup
2个回答
1
投票

我建议将您的属性实现为私有设置属性,并且只允许通过方法调用设置值。

虽然这是个人偏好,但在我看来,从属性设置者那里抛出异常似乎是不好的做法。
作为要使用此类的人,我宁愿处理

TrySet
方法,而不是属性设置器中的潜在异常:

public class User
{
    public string UserName { get; private set; }
    public string Password { get; private set; }
    public bool IsReadOnly {get; private set;}
 
    public void MakeReadOnly() => IsReadOnly = true; 

    public bool TrySetUserName(string userName) 
    {
        if(IsReadOnly) return false;
        UserName = userName;
        return true;
    }
}

更新

我花了一些时间研究如何将其推广到界面中。我有一个想法,使用通用的

TrySetValue
方法,该方法将接收要设置为 lambda 表达式和值的属性,并且仅在实例尚未只读时才设置它。
我对使用表达式树的经验很少,所以这也是我学习一些新的很酷技巧的一种方式 - 这就是我想到的:(基于 Darin Dimitrov 的 stackoverflow 答案):

public interface IReadOnlyAbility
{
    bool IsReadOnly {get;}
    void MakeReadonly();
}

public class User : IReadOnlyAbility {
    
    public bool IsReadOnly {get; private set;}
    public string UserName { get; private set; }
    public string Password { get; private set; }
    
    public void MakeReadonly() => IsReadOnly = true;
}

这就是奇迹发生的地方:

public static class IReadOnlyAbilityExtensions
{
    public static bool TrySetValue<T, TValue>(this T target, Expression<Func<T, TValue>> memberLambda, TValue value)
        where T : IReadOnlyAbility
    {
        if(target.IsReadOnly) return false;
            
        if (memberLambda.Body is MemberExpression memberSelectorExpression
            && memberSelectorExpression.Member is PropertyInfo property 
            && property.Name != nameof(target.IsReadOnly))
        {
            property.SetValue(target, value, null);
            return true;
        }
        
        return false;
    }
}

现在可以按如下方式使用:

var user = new User();
// these will return true
user.TrySetValue(c => c.UserName, "User name"); 
user.TrySetValue(c => c.Password, "Password");

// this will return false, you can't use this method to change the IsReadOnly property
user.TrySetValue(c => c.IsReadOnly, true);

user.MakeReadonly();

// these will return false
user.TrySetValue(c => c.IsReadOnly, false);
user.TrySetValue(c => c.UserName, "some other value");
user.TrySetValue(c => c.Password, "some other password");
    

您可以在 .net fiddle

上观看现场演示

0
投票

我检查了建议的方法,为每个初始化调用 TrySetValue 有点令人困惑,我为我的项目使用 Lib.Harmony 定义了一个替代解决方案,在我看来,这在编码方面更干净。

Typescript 的 Readonly 解决了这个问题。

我使用以下nuget

NuGet\Install-Package Lib.Harmony -版本 2.2.2

IReadOnlyAbility 是一个接口,其实现使对象的所有可配置公共属性默认为只读。

public interface IReadOnlyAbility
{
    bool IsReadOnly { get; internal set; }
    void ReadOnly() ;
}

通过使用 ReadOnlyAbilityAttribute 可以设置不应该是只读的属性。

[AttributeUsage(AttributeTargets.Property)]
public class ReadOnlyAbilityAttribute : Attribute
{
    public bool Check { get; }
    public ReadOnlyAbilityAttribute(bool check) => Check = check;
}

ReadOnlyAbilityInstaller 负责为实现 IReadOnlyAbility 的类添加只读检查代码

public sealed class ReadOnlyAbilityInstaller
{
    private readonly Lazy<Harmony> HarmonyInstance = new Lazy<Harmony>(() => new Harmony($"Harmony{Guid.NewGuid().ToString().Replace("-", string.Empty)}"));
    public void Install(Assembly assembly)
    {
        var readOnlyAbilityType = typeof(IReadOnlyAbility);
        var readOnlyAbilityTypes = assembly.GetTypes().Where(readOnlyAbilityType.IsAssignableFrom).Where(q => q.IsClass || q.IsValueType).ToList();

        var prefix = typeof(ReadOnlyAbilityInstaller).GetMethod(nameof(PrefixMethod), BindingFlags.NonPublic | BindingFlags.Static);
        var prefixHarmonyMethod = new HarmonyMethod(prefix);
        foreach (var type in readOnlyAbilityTypes)
        {
            var properties = type.GetProperties().Where(q => q.GetCustomAttribute<ReadOnlyAbilityAttribute>()?.Check != false)
                                                 .Where(q => q.SetMethod is not null)
                                                 .Select(q => q.SetMethod!)
                                                 .Where(q => q.IsPrivate == false)
                                                 .ToList();

            foreach (var property in properties)
                HarmonyInstance.Value.Patch(property, prefixHarmonyMethod);

        }
    }
    private static bool PrefixMethod(IReadOnlyAbility __instance, MethodBase __originalMethod)
    {
        if (__instance.IsReadOnly)
            throw new ReadOnlyException($"Current instance is readonly cause «{__originalMethod.Name.Replace("set_", string.Empty)}» can not be change");
        return true;
    }
}

现在需要安装它:

internal static class Program
{
    [STAThread]
    static void Main()
    {
        var assembly = Assembly.GetExecutingAssembly();
        var readOnlyAbilityInstaller = new ReadOnlyAbilityInstaller();
        readOnlyAbilityInstaller.Install(assembly);
        /*other source code*/
    }
}

例如:

public class ServiceConfig : IReadOnlyAbility
{
    public string IP { get; set; }
    public int Port { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public bool IsReadOnly { get; private set; }
    public void ReadOnly() => IsReadOnly = true;
}

public class Service
{
    public readonly ServiceConfig Config;
    public Service(ServiceConfig config)
    {
        Config = config;
        Config.ReadOnly();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.