【问题标题】:Implementing an interface with a generic parameter on a F# record在 F# 记录上实现具有泛型参数的接口
【发布时间】:2022-01-27 23:06:17
【问题描述】:

我正在尝试在 F# Record 上实现 Microsoft.Extensions.Logging.ILogger(为简洁起见,复制如下)

using System;

namespace Microsoft.Extensions.Logging
{
    public interface ILogger
    {
        void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter);
        bool IsEnabled(LogLevel logLevel);
        IDisposable BeginScope<TState>(TState state);
    }
}

这是记录实现。

  type ILoggerRcd<'TState> = 
    {
      BeginScope : 'TState -> IDisposable
      IsEnabled : LogLevel -> bool
      Log : LogLevel * EventId * 'TState * exn * Func<'TState,exn,string> -> unit
    }
    interface ILogger with
          override this.BeginScope(state : 'TState): IDisposable = 
              this.BeginScope (state)
          override this.IsEnabled(logLevel: LogLevel): bool = 
              this.IsEnabled logLevel
          override this.Log(logLevel: LogLevel, eventId: EventId, state : 'TState, ``exception``: exn, formatter: Func<'TState,exn,string>): unit = 
              this.Log(logLevel, eventId, state, ``exception``, formatter)

但是,我在 BeginScope 上收到此错误:One or more of the explicit class or function type variables for this binding could not be generalized, because they were constrained to other types

日志中出现此错误:The generic member 'Log' has been used at a non-uniform instantiation prior to this program point. Consider reordering the members so this member occurs first. Alternatively, specify the full type of the member explicitly, including argument types, return type and any additional generic parameters and constraints.

我已经阅读了一些关于 fsharp 编译器本身的问题,但似乎没有什么是我遇到的这种情况。这是可以做到的吗?

【问题讨论】:

  • 您是否尝试过让记录中的字段名称与 ILogger 的成员名称不同?不确定这是否会使编译器感到困惑。

标签: c# .net logging f#


【解决方案1】:

这里的问题是您试图约束ILogger'TState 以匹配ILoggerRcd'TState,但这不是您的选择。

要看到这一点,请注意ILogger.BeginScope 的调用者可以在一次调用中传递int 状态,然后在另一次调用中传递string 状态到同一个ILogger 实例 .您的实现试图阻止这种情况,因此会出现编译器错误。

我能看到的唯一方法是在你的类型中使用泛型方法而不是函数记录。我认为没有任何方法可以使用普通 F# 记录来做你想做的事。

【讨论】:

    【解决方案2】:

    ILogger 接口要求您可以记录 any 类型的对象,但您试图仅记录 'TState 类型的对象。

    接受BeginScope的签名:

    IDisposable BeginScope<TState>(TState state);
    

    看到&lt;TState&gt; 位了吗?这是一个通用参数。这个签名意味着每次有人调用这个方法时,他们可以选择一个类型 TState 用于该调用。

    再次重申:调用者选择泛型类型,而不是实现者。

    例如:

    let l : ILogger = ...
    l.BeginScope 42   // TState = int
    l.BeginScope true // TState = bool
    

    这意味着BeginScope 的实现必须能够使用任何 类型,而不仅仅是创建ILoggerRcd 记录的类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多