我已经按照下面描述的方式解决了我的问题,但我想找到最好的方法。
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();
我建议将您的属性实现为私有设置属性,并且只允许通过方法调用设置值。
虽然这是个人偏好,但在我看来,从属性设置者那里抛出异常似乎是不好的做法。
作为要使用此类的人,我宁愿处理
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 表达式和值的属性,并且仅在实例尚未只读时才设置它。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
上观看现场演示我检查了建议的方法,为每个初始化调用 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();
}
}