puerts icon indicating copy to clipboard operation
puerts copied to clipboard

[Unity] 父类为泛型类且包含返回泛型的静态函数时,子类可以覆盖声明该函数以获得准确返回类型

Open zombieyang opened this issue 2 years ago • 10 comments

相关issue:#216

class CB<T> {
    public static GetInstance (): any // 因为#216原因不得不写成any,在C#侧它是T
}
class CBD extends CB$1<CBD> {
    public static GetInstance (): CBD
}

zombieyang avatar Aug 22 '23 04:08 zombieyang

这样处理起来感觉超麻烦的啊, 假定它是这样的嵌套类型呢:

class ClassB<T> {
	 public static mthod1(): System.Collections.Generic.List$1<T>;
	 public static mthod2(): System.Array$1<System.Array$1<T>>;
 }

throw-out avatar Aug 24 '23 06:08 throw-out

之前到时考虑过使用这样的模式进行声明, 但是因为不能支持getter和setter, 所以暂时搁置了:

type ConstructorType<T> = { new(...args: any[]): T };

class ClassA<T> {
	public static GetInstance<T extends ClassA<any>>(this: ConstructorType<T>): T;
	public static GetGenericTypeList<T extends ClassA<any>>(this: ConstructorType<T>): CS.System.Collections.Generic.List$1<T>;
  }
class ClassB extends ClassA<ClassB>{

}
class ClassC extends ClassB {

}

使用是这样的:

let objB = ClassB.GetInstance();        //objB is ClassB
let objC = ClassC.GetInstance();        //objB is ClassC

let listB = ClassB.GetGenericTypeList();     //listB is System.Collections.Generic.List$1<ClassB>
let listC = ClassC.GetGenericTypeList();     //listC is System.Collections.Generic.List$1<ClassC>

throw-out avatar Aug 24 '23 06:08 throw-out

这样处理起来感觉超麻烦的啊, 假定它是这样的嵌套类型呢:

class ClassB<T> {
	 public static mthod1(): System.Collections.Generic.List$1<T>;
	 public static mthod2(): System.Array$1<System.Array$1<T>>;
 }

也能做到吧。

这个方案只是在“有子类继承它时,让子类的类型声明可用”,没有解决直接用这个父类的问题,如果是继承的时候,你这个case应该也能跑的?

这是内部一个项目组的办法。

zombieyang avatar Aug 24 '23 07:08 zombieyang

有一个可能比较完美的解决方案。但是与之前用法不同的。

比如对于Singleton<T>,换成如下实现方式(当伪代码看):

function Singleton<T>(typeT: T) {
      if (!this == global) { throw new Error('Singleton is a genericTypeDefinition and should call as Singleton(System.Type) first'); }
      return class Singleton$T extends System.Object {
            public static getInstance(): T;
      }
}
var SingletonGameObject = Singleton<GameObject>(puer.$typeof(UnityEngine.GameObject));
var singleGO = new SingletonGameObject();

这样SingletonGameObject的静态方法就有正常的类型,这个泛型类的用法(通过一次函数调用而不是puer.$generic得到具体泛型类)也比较符合新手的直觉。有个叫Jint的库是这么做的(不过它们对于非泛型类也要通过函数调用才能得到类,很奇怪,而且他们也没支持DTS)

但目前不太可能粗暴地替换原来的办法了,如果可以以扩展包的方式提供也不错。

zombieyang avatar Aug 24 '23 07:08 zombieyang

尝试了一下, 是可行的:

declare global {
    const __static_generic_arguments_builder__: unique symbol;
    class ClassA<T> {
        public self: T;
        public field1: string;

        public static get Instance(): any;
        public static GetInstance(): any;

        protected static [__static_generic_arguments_builder__]<T>(): {
            //实现构造函数
            new(...args: any): ClassA<T>;
            //...
            //....
            //实现静态成员
            readonly Instance: T;
            GetInstance(): T;
        }
    }
    function ClassA_static_generic_arguments_builder<T>(): {
        //实现构造函数
        new(...args: any): ClassA<T>;
        //...
        //....
        //实现静态成员
        readonly Instance: T;
        GetInstance(): T;
    };
    class ClassB extends ClassA_static_generic_arguments_builder<ClassB>() {

    }
    class ClassC extends ClassA[__static_generic_arguments_builder__]<ClassC>() {

    }
}

let objB = ClassB.GetInstance();        //type is ClassB
let objBself = objB.self;               //type is ClassB

let objC = ClassC.GetInstance();        //type is ClassC
let objCself = objC.self;               //type is ClassC

throw-out avatar Aug 24 '23 08:08 throw-out

用unique symbol声明更好, 避免污染类型环境

throw-out avatar Aug 24 '23 08:08 throw-out

如果把使用了泛型参数的静态成员移除的话, 可以直接简写成:

class ClassA<T> {
        public self: T;
        public field1: string;

        protected static [__static_generic_arguments_builder__]<T>(): typeof ClassA<T> & {
            //实现使用了泛型参数的静态成员
            readonly Instance: T;
            GetInstance(): T;
        }
    }

throw-out avatar Aug 24 '23 08:08 throw-out

现在dts里genericDefinition的声明是类似List$1<T>这样的,List<T>还可以用

zombieyang avatar Aug 24 '23 12:08 zombieyang

现在dts里genericDefinition的声明是类似List$1<T>这样的,List<T>还可以用

List<T>应该不行, 首先是会污染类型环境, 开发者可能将其作为实际的值使用. 其次无法区分C#中相同类名的类型:

class ClassA<T>{ }
class ClassA<T1, T2>{ }

throw-out avatar Aug 25 '23 02:08 throw-out

尝试了一下, 是可行的:

declare global {
    const __static_generic_arguments_builder__: unique symbol;
    class ClassA<T> {
        public self: T;
        public field1: string;

        public static get Instance(): any;
        public static GetInstance(): any;

        protected static [__static_generic_arguments_builder__]<T>(): {
            //实现构造函数
            new(...args: any): ClassA<T>;
            //...
            //....
            //实现静态成员
            readonly Instance: T;
            GetInstance(): T;
        }
    }
    function ClassA_static_generic_arguments_builder<T>(): {
        //实现构造函数
        new(...args: any): ClassA<T>;
        //...
        //....
        //实现静态成员
        readonly Instance: T;
        GetInstance(): T;
    };
    class ClassB extends ClassA_static_generic_arguments_builder<ClassB>() {

    }
    class ClassC extends ClassA[__static_generic_arguments_builder__]<ClassC>() {

    }
}

let objB = ClassB.GetInstance();        //type is ClassB
let objBself = objB.self;               //type is ClassB

let objC = ClassC.GetInstance();        //type is ClassC
let objCself = objC.self;               //type is ClassC

试了多种组合方式, 好像都需要手动声明一遍构造函数, 但最终发现只需要交换一下它们的位置就能调用到想要的方法(😂):

const __static_generic_arguments_builder__: unique symbol;
//合并两个类型,key相同的类型由第二个覆盖第一个
type Merge<First, Second> = {
    [P in keyof First | keyof Second]: P extends keyof Second ? Second[P] : P extends keyof First ? First[P] : never
}

class ClassA<T> {
    public self: T;
    public field1: string;

    public static get Instance(): any;
    public static GetInstance(): any;

    protected static [__static_generic_arguments_builder__]<T>(): {
        //实现泛型参数静态成员
        readonly Instance: T;
        GetInstance(): T;
    } & typeof ClassA<T>;

    /* protected static [__static_generic_arguments_builder__]<T>(): Merge<typeof ClassA<T>, {
        //实现泛型参数静态成员
        readonly Instance: T;
        GetInstance(): T;
    }> & {
        //实现构造函数
        new(...args: any): ClassA<T>;
        //...
        //....
    }; */
}

throw-out avatar Aug 25 '23 02:08 throw-out