简体   繁体   English

是否可以将泛型参数类型限制于此?

[英]Is it possible to constrain a generic parameter type to this?

Short version精简版

How do I force the BaseClass 's TModel generic parameter to be of the same type as the class that derives from it?如何强制BaseClassTModel泛型参数与派生自它的类的类型相同?

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

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

In this example, how do I force the BaseClass 's TModel to be of type Person and not something else?在此示例中,如何强制BaseClassTModelPerson类型而不是其他类型?

This is invalid syntax, but it's what I'm imagining:这是无效的语法,但这是我的想象:

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

public class Person : BaseClass<PersonValidator> { }

Is this somehow possible or should I use a totally different solution to achieve this?这是可能的还是我应该使用完全不同的解决方案来实现这一点?

Long version长版

I'm trying to extract some validation logic into a base class, but I don't know how to constraint the generic types so the resulting base class is fully fool-proof.我正在尝试将一些验证逻辑提取到基类中,但我不知道如何约束泛型类型,因此生成的基类完全是万无一失的。

Here's an example of what all the validation logic looks like without a base class.这是一个没有基类的所有验证逻辑的示例。 I'm using FluentValidation to validate the object and I'm exposing that validation result via the IDataErrorInfo interface so it can be used by the WPF UI.我正在使用 FluentValidation 验证对象,并通过IDataErrorInfo接口公开该验证结果,以便 WPF UI 可以使用它。

Original solution原始解决方案

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);
    }
}

Validation implementation separated into a base class验证实现分离为一个基类

I'd like to separate the validation logic and IDataErrorInfo implementation into a base class so this boilerplate doesn't have to be repeated in every model class.我想将验证逻辑和IDataErrorInfo实现分离到一个基类中,这样就不必在每个模型类中重复这个样板。 Here's what I have.这就是我所拥有的。

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);
}

And here's how I'm using it:这是我使用它的方式:

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

The problem问题

The problem I have with this solution is that you can write this invalid code:我对这个解决方案的问题是你可以编写这个无效的代码:

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

Is this what you are looking for?这是你想要的?

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>
{
}

This is a classic pattern where a generic parameter is constrained to the derived class.这是一个经典模式,其中泛型参数被限制在派生类中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM