是否可以将通用参数类型约束为此?

Is it possible to constrain a generic parameter type to this?

短版

如何强制 BaseClassTModel 泛型参数与派生自它的 class 属于同一类型?

public class BaseClass<TModel, TValidator> where TValidator : IValidator<TModel> { }

public class Person : BaseClass<Person, PersonValidator> { }

在此示例中,我如何强制 BaseClassTModelPerson 类型而不是其他类型?

这是无效的语法,但这是我想象的:

public class BaseClass<TValidator> where TValidator : IValidator<this> { }

public class Person : BaseClass<PersonValidator> { }

这是否可能,或者我应该使用完全不同的解决方案来实现这一目标?

长版

我正在尝试将一些验证逻辑提取到基 class 中,但我不知道如何限制泛型类型,因此生成的基 class 是完全安全的。

这是一个没有基础的所有验证逻辑的示例 class。我正在使用 FluentValidation 来验证对象,我通过 IDataErrorInfo 接口公开该验证结果,以便 WPF UI.

可以使用它

原解

public class User : IDataErrorInfo 
{
    private readonly IValidator<Person> _validator = new();

    public string Username { get; set; }
    public string Password { get; set; }

    private string ValidateAndGetErrorForProperty(string propertyName)
    {
        var result = _validator.Validate(this);

        if (result.IsValid)
        {
            return string.Empty;
        }

        return result.Errors.FirstOrDefault(a => a.PropertyName == propertyName)?.ErrorMessage ?? string.Empty;
    }

    //IDataErrorInfo implementation
    public string Error => string.Empty;
    public string this[string columnName] => ValidateAndGetErrorForProperty(columnName);
}

public class UserValidator : AbstractValidator<User>
{
    public UserValidator()
    {
        RuleFor(a => a.Username)
            .EmailAddress();

        RuleFor(a => a.Password)
            .MinimumLength(12);
    }
}

验证实现分离成一个基class

我想将验证逻辑和 IDataErrorInfo 实现分离到一个基础 class 中,这样就不必在每个模型 class 中重复此样板文件。这是我的。

public abstract class ValidationBase<TModel, TValidator> : IDataErrorInfo where TValidator : IValidator<TModel>, new()
{
    private readonly TValidator _validator;

    public ValidationBase()
    {
        _validator = Activator.CreateInstance<TValidator>();
    }

    private string ValidateAndGetErrorForProperty(string propertyName)
    {
        //I have to check if this is of type TModel since the TModel isn't constraint to this
        if (this is not TModel model)
        {
            throw new InvalidOperationException($"Instance is not of the supported type: {typeof(TModel)}. Type of {GetType()} found instead");
        }

        var result = _validator.Validate(model);

        if (result.IsValid)
        {
            return string.Empty;
        }

        return result.Errors.FirstOrDefault(a => a.PropertyName == propertyName)?.ErrorMessage ?? string.Empty;
    }

    //IDataErrorInfo implementation
    public string Error => string.Empty;
    public string this[string columnName] => ValidateAndGetErrorForProperty(columnName);
}

下面是我的使用方法:

public class User : ValidationBase<User, UserValidator>
{
    public string Username { get; set; }
    public string Password { get; set; }
}

问题

这个解决方案的问题是你可以写这个无效的代码:

public class InvalidClass : ValidationBase<User, UserValidator>
{
    
}

这是您要找的吗?

public interface IValidator<TModel>
{
}

public class BaseClass<TModel, TValidator> 
    where TModel : BaseClass<TModel, TValidator> 
    where TValidator 
    : IValidator<TModel> { }

// Only classes derived from BaseClass can be instantiated
public class Person 
    : BaseClass<Person, PersonValidator> { }

public class PersonValidator 
    : IValidator<Person>
{
}

这是一个 classic 模式,其中通用参数被限制为派生的 class。