类型作为参数传递的递归泛型函数 [英] Recursive generic function with type passed as a parameter

查看:175
本文介绍了类型作为参数传递的递归泛型函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想拥有一个递归的泛型函数,但是我不能使用作为泛型函数调用中的参数传递的类型,原因是

I want to have a recursive generic function, but I cannot use type passed as an argument in generic function invocation, cause of

'memberType' refers to a value, but is being used as a type here.

是否可以将 memberType 传递给

示例:

class Packet {
  header: Header
  body: Body

  static MEMBERS = [
    ['header', Header, 0,  6],
    ['body',   Body,   6, 10],
  ]
}

class Header {
  size: number
  ttl: number

  static MEMBERS = [
      ['size', 'number',   0,  2],
      ['ttl',  'number',   2,  3],
  ]
}

class Body {
  raw: string

  static MEMBERS = [
      ['raw', 'string',   0,  10]
  ]
}




class Deserializer {
  static Deserialize<T>(serialized: string, type: { new(): T ;}): T {
      const obj = new type();

      const members = obj.constructor['MEMBERS'];

      for(const member of members) {
          var [memberName, memberType, startOffset, len] = member;
          const serializedMember = serialized.substr(startOffset, len) // cut string holding serialized member (like 'qwertyuiop' from example)

          if(memberType == 'number') { // handle primitive type differently
            obj[memberName] = parseInt(serializedMember); 
          } else if(memberType == 'string') { // handle primitive type differently
            obj[memberName] = serializedMember

          } else { // handle non primitives, like Header and Body

// *** issue is on the following line ***
            obj[memberName] = Deserialize<memberType>() // 'memberType' refers to a value, but is being used as a type here.
          }

      }

      return obj as T;
  }
}

//                  HEADRBOOOOOOODY - description of `serialized` string below
//                  SSTTL (where SS = header.size, TTL = header.ttl)  
const serialized = '11222qwertyuiop'
const packet: Packet = Deserializer.Deserialize<Packet>(serialized, Packet)

// expected `packet` object structure after running `Deserialize`
// Packet
//   * header -> Header
//                 * size -> 11
//                 * ttl  -> 222
//   * body -> Body
//               * raw -> BOOOOOOODY


推荐答案

对, memberType 是一个值,而不是类型。您可以从编译器获取其类型为 typeof memberType ,但对于您而言,对 Deserialize 的递归调用需要参数,如果在其中传递通用类型参数,则可以将其从 typeof memberType 推断出来:

Right, memberType is a value, not a type. You could get its type from the compiler as typeof memberType, but in your case the recursive call to Deserialize needs arguments, from which the generic type parameter can be inferred as typeof memberType if you pass it in:

obj[memberName] = Deserializer.Deserialize(serializedMember, memberType);

这应该可以解决您提出的问题,但是示例代码中有很多类型错误,并且您可能要解决的隐式任何问题。

That should clear up the problem you asked about, but the example code has a lot of type errors and implicit any issues which you should probably address.

一种方法要解决这些问题,请稍加重构,并明智地使用类型断言来处理编译器无法验证的安全位置(例如 obj [memberName] = parseInt(...)不能工作,断言,因为编译器不会意识到 memberName 处的属性应为 number ,因为它是一个更高阶的推断它无法执行)。也许像这样:

One way to address them is via slight refactoring and the judicious use of type assertions to deal with the places that the compiler can't verify as safe (e.g. obj[memberName] = parseInt(...) won't work without an assertion because the compiler won't realize that the property at memberName should be a number, since it's a higher order inference it can't perform). Maybe something like:

type DeserializableClass<T> = {
  new(): T,
  MEMBERS: readonly {
    [K in keyof T]: readonly [K, string | DeserializableClass<T[K]>, number, number]
  }[keyof T][]
}

class Deserializer {
  static Deserialize<T>(serialized: string, type: DeserializableClass<T>): T {
    const obj = new type();

    const members = type.MEMBERS;

    for (const member of members) {
      var [memberName, memberType, startOffset, len] = member;
      const serializedMember = serialized.substr(startOffset, len) // cut string holding serialized member (like 'qwertyuiop' from example)

      if (typeof memberType !== "string") {
        obj[memberName] = Deserializer.Deserialize(serializedMember, memberType);
      } else if (memberType == 'number') { // handle primitive type differently
        obj[memberName] = parseInt(serializedMember) as any as T[keyof T];
      } else if (memberType == 'string') { // handle primitive type differently
        obj[memberName] = serializedMember as any as T[keyof T];
      }

    }
    return obj;
  }
}

在这里,我们更准确地描述了哪种类可以反序列化:它必须具有 MEMBERS 属性,并具有正确的元组数组。请注意,这并不是绝对安全的类型,因为 T [keyof T] 太宽了,您可以不断更改内容以使其更安全,但是到那时我会倾向于使您的 MEMBERS 属性也为基元保留反序列化器,并放弃字符串。请注意,编译器如何认为递归的 Deserialize 调用是可接受的,但是仍然需要告知 string 个数的情况起作用。

Here we've described more exactly what kind of class can be deserialized: it has to have a MEMBERS property with the right sort of array of tuples. Note that this isn't perfectly type safe because T[keyof T] is too wide, and you can keep changing things to be safer, but at that point I'd be inclined to make your MEMBERS property hold deserializers for primitives also and give up on the strings. Note how the compiler sees the recursive Deserialize call as acceptable, but it still needs to be told that the string and number cases work.

此外,为了接受 Packet ,您需要确保您的 MEMBERS 静态属性足够文字(元组通常会扩展为数组);一种简单的方法是使用 const 断言,例如:

Also, in order for this to accept Packet you need to ensure that your MEMBERS static properties are sufficiently literal (tuples tend to get widened to arrays); an easy way to do that is with a const assertion, like:

static MEMBERS = [
  ['size', 'number', 0, 2],
  ['ttl', 'number', 2, 3],
] as const






无论如何,到目前为止,我不会比现在更深入地进行重构。希望这可以帮助。祝你好运!


Anyway I won't go farther into the refactoring than this for now. Hope this helps. Good luck!

游乐场链路进行编码

这篇关于类型作为参数传递的递归泛型函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆