C# 调用具有不同类型的相同扩展函​​数作为参数(可能是委托?)

Posted

技术标签:

【中文标题】C# 调用具有不同类型的相同扩展函​​数作为参数(可能是委托?)【英文标题】:C# call same extended function with different type as parameter (maybe delegates ?) 【发布时间】:2021-11-13 00:17:24 【问题描述】:

我正在尝试为我的项目构建一个规则检查器,该检查器在规则列表中循环,如果其中任何一个的标志为真,则返回我。 下面是我作为示例编写的代码(不是实际代码,请忽略最终未观察到的最佳实践或细节)。

using System.Collections;
using System.Collections.Generic;
using System.Linq;

public interface ICounterValued 
    int CounterValue get;
    int CounterLimit get;


public interface ITimeValued 
    float ElapsedTime get;
    float TimeLimit get;


public class MyMainClass : ICounterValued , ITimeValued 
    // Counter data
    private int     _counterValue   = 0;
    private int     _counterLimit   = 10;
    // Time data
    private float   _elapsedTime    = 0;
    private float   _timeLimit      = 10f;
    // ICounterValued
    public int      CounterValue    => _counterValue;
    public int      CounterLimit    => _counterLimit;
    // ITimeValued
    public float    ElapsedTime     => _elapsedTime;
    public float    TimeLimit       => _timeLimit;

    public RuleChecker  ruleChecker;

    public void InitializeMe()
        ruleChecker = new RuleChecker();
        ruleChecker.rules.Add( new TimeRule() );
        ruleChecker.rules.Add( new CounterRule() );
    

    public void UpdateRuleChecker()
        ruleChecker.CheckRules<MyMainClass>( this );
    

    public bool NeedToStop()
        return ruleChecker.SomeRuleTriggered();
    


public class RuleChecker 
    public List<Rule> rules = new List<Rule>();

    public void CheckRules<T>( T data )
        rules.ForEach( x => x.UpdateMe<T>(data) );
    

    public bool SomeRuleTriggered()
        return rules.Any( x => x.IsTriggered() );
    


public abstract class Rule 
    protected bool _triggered;

    public virtual bool IsTriggered() =>  _triggered;
    // check logic that will set the _triggered flag to true
    public abstract void UpdateMe<T>( T data );


public class TimeRule : Rule 
    public override void UpdateMe<ITimeValued>( ITimeValued data )
        _triggered = (data.ElapsedTime >= data.TimeLimit);
    


public class CounterRule : Rule 
    public override void UpdateMe<ICounterValued>( ICounterValued data )
        _triggered = (data.CounterValue >= data.CounterLimit);
    

这样做的目的是在未来对具有一个或多个接口的不同“MainClasses”使用相同的规则检查器,并且可以重用这些规则或添加新的规则,如具有自己的触发逻辑的“模块”。

上面的代码不起作用,因为当我调用通用 void(UpdateMe&lt;ICounterValued&gt;UpdateMe&lt;ITimeValued&gt;)时,数据值无法访问我在接口内定义的属性(CounterValueCounterLimitElapsedTimeTimeLimit)。

我假设这种 void 泛型方法存在一些转换或接口问题;我没有必要使用泛型,但我没有找到更聪明的解决方案;我已经在Rule 类中尝试了将代表作为UpdateMe 的参数的一些东西,但是没有任何东西可以用不同的类型参数进行扩展,并让规则检查器通过为每个参数传递MyMainClass 的实例来调用所有内容。

感谢 andavace 的任何建议。

【问题讨论】:

你得到的确切错误是什么,错误到底在哪里? 我想,但我不确定问题是你在Rule.UpdateMe&lt;T&gt; 中对T 没有限制,允许访问属性。我想我可以创建一个基本接口public interface IValued int Value get; int Limit get; ,然后显式实现ICounterValuedITimeValued(暂时忽略int/float)。到目前为止我找不到魔法酱。我希望这个漫无边际的帮助 @Flydog57 错误为'ITimeValued' does not contain a definition for 'ElapsedTime' and no accessible extension method 'ElapsedTime' accepting a first argument of type 'ITimeValued' could be found (are you missing a using directive or an assembly reference?),所有其他属性都相同。如果我总是知道界面内将包含什么类型的数据,则可以完成您的解决方案。我正在尝试实现的实际用途,例如,使用更复杂的接口,例如:``` public interface ITimer AdvancedTimer Timer get; ``` 是的,我复制了你的代码并试图让它编译,得到了同样的错误。这是因为T 缺少约束。你不能在T 上调用某些东西,除非T 以允许调用发生的方式受到限制。我现在想已经太晚了,但你的问题让我很感兴趣。一旦我离开,我可能会想出一个解决方案。 @Flydog57 我认为你是对的,UpdateMe 的覆盖使 data 属性被视为通用 T 值,而不是被覆盖的接口类型。泛型解决方案看起来太有限,无法做如此不均匀的事情;也许与返回 bool 的代表的事情是可行的。 【参考方案1】:

以这种方式构建(您的方法)对我来说更有意义:

public class RuledClass
    public List<Rule> Rules = new List<Rule>();
    
    public bool AnyViolated()
        return Rules.Any(r => r.IsViolated());
    


public class MyMainClass : RuledClass, ICounterValued , ITimeValued 
    // Counter data
    public int     CounterValue get;set; 
    public int    CounterLimit  get;set; 
    // Time data
    public float  ElapsedTime   get;set; 
    public float  TimeLimit   get;set;   
    // ICounterValued
    

    public MyMainClass()
        Rules.Add( new TimeRule(this) );
        Rules.Add( new CounterRule(this) );
    


public abstract class Rule 
    public abstract bool IsViolated();


public class TimeRule : Rule 
    private ITimeValued _thing;
    public TimeRule(ITimeValued thing)
        _thing = thing;
    
    public override bool IsViolated()
        return (_thing.ElapsedTime >= _thing.TimeLimit);
    

public class CounterRule : Rule 
    private ICounterValued _thing;
    public CounterRule(ICounterValued thing)
        _thing = thing;
    
    public override bool IsViolated()
        return (_thing.CounterValue >= _thing.CounterLimit);
    

按照你的方法,我无法弄清楚为什么 X 类会有一个可以将 Y 类传递给的规则列表;它们适用的规则和类在我的脑海中是联系在一起的。因此我在构建时建立了这个链接

..但即使那样我也不知道我是否会这样做

我想我更有可能做这样的事情:

public class RuledClass
    public List<Func<bool>> Rules = new List<Func<bool>>();
    
    public bool AnyViolated()
        return Rules.Any(r => r());
    


public class MyMainClass : RuledClass 
    // Counter data
    public int     CounterValue get;set; 
    public int    CounterLimit  get;set; 
    // Time data
    public float  ElapsedTime   get;set; 
    public float  TimeLimit   get;set;   
    // ICounterValued
    

    public MyMainClass()
        Rules.Add(()=>ElapsedTime>TimeLimit);
        Rules.Add(()=>CounterValue>CounterLimit);
    

【讨论】:

第一个代码是最接近的,我只是在我的代码中尝试了解决方案,通过一些调整,我可以让它在规则的构造函数中发送实例并将 MyMainClass 实例存储为像你一样的接口写道:private ICounterValued _thing;。扩展 RuledClass 不是一个好主意,因为 MyMainClass 通常需要扩展另一个不是 Ruled 的基类。第二种解决方案过于面向我的示例代码,例如,我必须在每个需要 TimeLimit 规则或 Counter Rule 的类中编写“违规逻辑”。 我只做了一个 RuledClass 因为我不想每次都提供一个 rules 属性定义;如果您乐于在 IRuleable 的任何地方重复规则定义,则可以将其设为接口。 第二种解决方案过于以我的示例代码为导向 - 这就是我害怕提供人为示例的结果.. 我必须编写“违规逻辑” - 但是通过使其成为接口,您必须“在每个需要 X 的类中编写更多属性”。第二个的逻辑非常简单,并且确实允许一个类设置自己疯狂的变化规则。 ..没有无休止的接口,它们的实现以及支持它们的实例。.正如所呈现的那样,它使一个类负责检查它是否有效,但我认为没有理由外部类无法给出位于数组中的规则集Funcs【参考方案2】:

我找到了一个似乎可行的解决方案,并且与我正在寻找的解决方案接近。 @Caius Jard 的第一个解决方案也有效,但我认为最好避免将检查实例指向规则的变量中。 在我的问题中的相同示例代码下方,但更新代码旁边有 cmets:

using System.Collections;
using System.Collections.Generic;
using System.Linq;

public interface ICounterValued 
    int CounterValue get;
    int CounterLimit get;


public interface ITimeValued 
    float ElapsedTime get;
    float TimeLimit get;


public class MyMainClass : ICounterValued , ITimeValued 
    // Counter data
    private int     _counterValue   = 0;
    private int     _counterLimit   = 10;
    // Time data
    private float   _elapsedTime    = 0;
    private float   _timeLimit      = 10f;
    // ICounterValued
    public int      CounterValue    => _counterValue;
    public int      CounterLimit    => _counterLimit;
    // ITimeValued
    public float    ElapsedTime     => _elapsedTime;
    public float    TimeLimit       => _timeLimit;

    public RuleChecker  ruleChecker;

    public void InitializeMe()
        ruleChecker = new RuleChecker();
        ruleChecker.rules.Add( new TimeRule() );
        ruleChecker.rules.Add( new CounterRule() );
    

    // I don't need this anymore, the condition will be checked
    // inside SomeRuleTriggered
    // public void UpdateRuleChecker()
    //     ruleChecker.CheckRules<MyMainClass>( this );
    // 

    public bool NeedToStop()
        // The instance will be passed as argument
        // return ruleChecker.SomeRuleTriggered();
        return ruleChecker.SomeRuleTriggered( this );
    


public class RuleChecker 
    public List<Rule> rules = new List<Rule>();

    // As commented above, I don't need to update a stored 
    // flag in Rule (and direved) anymore
    // public void CheckRules<T>( T data )
    //     rules.ForEach( x => x.UpdateMe<T>(data) );
    // 

    // Now the instance will be passed as a generic object and parsed
    // inside IsTriggered method
    // public bool SomeRuleTriggered()
    //     return rules.Any( x => x.IsTriggered() );
    // 
    public bool SomeRuleTriggered( object target )
        return rules.Any( x => x.IsTriggered( target ) );
    


public abstract class Rule 
    // No need to update a falg anymore
    // protected bool _triggered;

    // No need to update a falg anymore
    // public virtual bool IsTriggered() =>  _triggered;

    // No need to update a falg anymore
    // public abstract void UpdateMe<T>( T data );
    public abstract bool IsTriggered( object target );


public class TimeRule : Rule 
    // No need to update a falg anymore
    // public override void UpdateMe<ITimeValued>( ITimeValued data )
    //     _triggered = (data.ElapsedTime >= data.TimeLimit);
    // 
    public override bool IsTriggered( object target )
        ITimeValued target_timed = target as ITimeValued;
        if( null == target_timed)
            return false;

        return (target_timed.ElapsedTime >= target_timed.TimeLimit);
    


public class CounterRule : Rule 
    // No need to update a falg anymore
    // public override void UpdateMe<ICounterValued>( ICounterValued data )
    //     _triggered = (data.CounterValue >= data.CounterLimit);
    // 
    public override bool IsTriggered( object target )
        ICounterValued target_counter = target as ICounterValued;
        if( null == target_counter)
            return false;

        return (target_counter.CounterValue >= target_counter.CounterLimit);
    

如您所见,RuleChecker 可以调用任何Rule 派生类的方法,将检查实例作为通用object 传递。任何扩展 Rule 的类都可以实现他的规则逻辑覆盖 IsTriggered,并在内部进行显式转换,然后最终开始检查。

【讨论】:

以上是关于C# 调用具有不同类型的相同扩展函​​数作为参数(可能是委托?)的主要内容,如果未能解决你的问题,请参考以下文章

虚函数与重载函数的区别

C# Specflow:如何在另一个文件夹的类中调用具有动态表作为参数的方法

Java中重载和重写的区别

TypeScript 具有相同参数的多种返回类型

C#中具有相同名称和签名但返回类型不同的方法

扩展1