Skip to content

FuncRef, FuncPureRef, Call и CallOn - ссылки на функции и их вызов

edited March 2022 in Tutorials

В OverScript функции нельзя использовать как объекты, но можно получать на них ссылки. Вызывать функции по ссылкам можно как на их родных объектах, так и на экземплярах любых своих типов.
Создаётся ссылка функцией FuncRef, а вызвать функцию по ссылке можно функцией Call.
Сначала простые примеры:

object fn=FuncRef(WriteLine("")); //ссылка на WriteLine(object), т.к. перегрузки для string нет
fn.Call("Hello, world!"); //Hello, world!

fn=FuncRef(Ceiling(0.0)); //ссылка на Ceiling(double), которая возвращает тип double
object x=fn.Call(4.9);
WriteLine(x+"; "+x.GetType()); //5; System.Double

fn=FuncRef(Ceiling(0.0F)); //ссылка на Ceiling(float), которая возвращает тип float (Single)
x=fn.Call(4.9);
WriteLine(x+"; "+x.GetType()); //5; System.Single

ReadKey();

Из примера видно, что функции для ссылок нужно указывать с аргументами, которые нужны для поиска нужной перегрузки. Имеют значение только их типы, т.е. можно написать FuncRef(Ceiling(0.0)), а можно FuncRef(Ceiling(5.0)) - результат будет один и тот же.
При вызове функции нужно передать аргументы того типа, которые она принимает. Многие функции конвертируют значения в нужные типы, но некоторые функции предназначены строго для определённых типов и работать не будут. Поэтому нужно внимательно относиться к типам передаваемых аргументов.
Лучше всего, чтобы случайно не указать аргумент не того типа, задавать типы так:

object fn=FuncRef(Test(int\));
fn.Call(123); //int: 123
fn=FuncRef(Test(string\));
fn.Call(123); //string: 123 //число автоматически конвертировалось в строку

Test(int v){
    WriteLine("int: "+v);
}
Test(string v){
    WriteLine("string: "+v);
}

В этом примере есть int\ и string\. Когда после типа идёт обратный слеш, то это значение по умолчанию для типа. Т.е. int\ - это как default(int) в C#, а string\ - как default(string). А что будет, если указать тип без слеша (FuncRef(Test(int))? Выскочит ошибка, т.к. интерпретатор будет искать перегрузку для object, ведь int это object содержащий тип.

object type=int; //в C# так не получится
WriteLine(type); //System.Int32

Теперь посмотрим, как ссылка на функцию захватывает ссылку на экземпляр:

Foo foo=new Foo("Hello!");
object fn=FuncRef(foo.Print());
fn.Call(); //Hello!
foo=new Foo("Test!");
fn.Call(); //Hello! //то же самое, хотя в foo новый экземпляр

class Foo{
    string Text;
    New(string text){
        Text=text;
    }
    public Print(){
        WriteLine(Text);
    }
}

FuncRef вернула ссылку на Print конкретного экземпляра. Если перезаписать foo новым экземпляром, вызываться будет Print на том экземпляре, который был в foo на момент вызова FuncRef.
Для получения ссылки на функцию без захвата конкретного экземпляра, есть функция FuncPureRef.

Foo foo=new Foo("Hello!");
object fn=FuncPureRef(foo.Print());
fn.CallOn(foo); //Hello!
foo=new Foo("Test!");
fn.CallOn(foo); //Test!

class Foo{
    string Text;
    New(string text){
        Text=text;
    }
    public Print(){
        WriteLine(Text);
    }
}

В этом примере вместо Call используем CallOn для вызова функции на указанном экземпляре. Кстати, CallOn
можно использовать и для ссылок полученных через FuncRef, а не через FuncPureRef.
CallOn можно использовать с экземпляром любого класса.

Foo foo=new Foo();
object fn=FuncPureRef(foo.Test());
fn.CallOn(foo); //Instance of Foo
Bar bar=new Bar();
fn.CallOn(bar); //Instance of Bar

class Foo{
    public Test(){
        WriteLine(this);
    }
}
class Bar{}

Ещё пример:

object fn=FuncPureRef(Test());
fn.CallOn(new Foo()); //Instance of Foo; X=10
fn.CallOn(new Bar()); //Instance of Bar; X=20

int X;
public Test(){
    WriteLine(this+"; X="+X);
}

class Foo{
    int X=10;
}
class Bar{
    int X=20;
}

Динамический вызов функций и динамическое получение ссылок

Динамически вызвать метод объекта можно функцией DynCall. Для динамического получения ссылки на функцию нужно использовать перегрузки FuncRef и FuncPureRef, принимающие более одного аргумента.
Посмотрим на примере:

object obj=new Foo();
//вызовем функцию Test(567) экземпляра в obj через DynCall(объект, имя_функции, аргумент_0, аргумент_1, ...)
//DynCall каждый раз ищет функцию по имени и типам аргументов
WriteLine(DynCall(obj, "Test", 567)); //At Foo: 567
obj=new Bar();
WriteLine(DynCall(obj, "Test", 567)); //At Bar: 567
WriteLine(DynCall(obj, "Test", 567L)); //At Bar: 567 long
WriteLine();
//теперь динамически получим ссылки на функции и выполним их обычными Call и CallOn
object fn=FuncRef(obj, "Test", long\); //получаем ссылку на Test(long) в Bar
WriteLine(fn.Call(567)); //At Bar: 567 long
obj=new Foo();
fn=FuncRef(obj, "Test", long\); //получаем ссылку на Test(int) в Foo (int потому, что для long нет перегрузки)
WriteLine(fn.Call(567)); //At Foo: 567

fn=FuncPureRef(obj, "Test", long\); //получаем ссылку без привязки к экземпляру
WriteLine(fn.CallOn(obj, 567)); //At Foo: 567

class Foo{
    static string Test(int x){return "At Foo: "+x;}
}
class Bar{
    public string Test(int x){return "At Bar: "+x;}
    string Test(long x){return "At Bar: "+x+" long";}
}

Как видите, можно вызывать не только публичные, но и приватные методы. А ещё можно вызывать функции статических классов:

object obj=Foo;
WriteLine(DynCall(obj, "Test", 555)); //At Foo: 555
obj=Bar;
WriteLine(DynCall(obj, "Test", 555)); //At Bar: 555

static class Foo{
    static string Test(int x){return "At Foo: "+x;}
}
static class Bar{
    static string Test(int x){return "At Bar: "+x;}
}

Кстати, CallOn тоже можно вызывать на статических классах:

Foo.PrintX(); //555
object fn=FuncPureRef(new Bar().SetX(0));
fn.CallOn(Foo, 777); //вызываем SetX из Bar на статическом классе Foo
Foo.PrintX(); //777

static class Foo{
    static int X=555;
    public static PrintX(){WriteLine(X);}
}
class Bar{
    int X;
    public SetX(int x){X=x;} //функция и X не статические, а экземплярные, т.к. статические переменные жестко привязаны к своему классу, и при вызове SetX на Foo будет меняться X в Bar, а не в Foo.
}

Этот пример может показаться очень странным, но на самом деле у каждого класса есть статический экземпляр (оксюморон, но, думаю, смысл понятен), в котором хранятся статические переменные. Этот экземпляр создаётся при первом обращении к его членам.

DynCall и динамические FuncRef и FuncPureRef работают медленно, т.к. при каждом вызове ищут нужную функцию. Обычные FuncRef и FuncPureRef (которые с одним аргументом-функцией) не ищут функцию, а просто получают её из выражения, в котором она уже найдена при загрузке кода.

Класс-интерфейс

В OverScript интерфейсов, как в C#, пока нет, но можно делать классы, имитирующие интерфейсы. Суть: два класса Bar и Baz наследуют от одного Foo, который у нас будет как бы интерфейсом. Мы сможем вызывать функцию Test не напрямую, а приводя объект к типу Foo.

Bar br=new Bar();
Baz bz=new Baz();
//проверим прямые вызовы:
br.Test(5); //At Bar: 5
bz.Test(5); //At Baz: 5
//а теперь вызовы через Test в Foo:
(Foo\br).Test(5); //At Bar: 5
(Foo\bz).Test(5); //At Baz: 5
//Напомню, что Foo\br - это то же самое, что (Foo)br. Можно и так и так писать:
((Foo)br).Test(5); //At Bar: 5
((Foo)bz).Test(5); //At Baz: 5

class Foo{
    object TestFn=FuncRef(this, "Test", int\);
    public Test(int x){
        TestFn.Call(x);
    }
}
class Bar:Foo{
    public Test(int x){
        WriteLine("At Bar: "+x);
    }
}
class Baz:Foo{
    public Test(int x){
        WriteLine("At Baz: "+x);
    }
}

Как интерпретатор видит классы Bar и Baz:

class Bar{
    object TestFn=FuncRef(this, "Test", int\);
    public Test(int x){
        TestFn.Call(x);
    }
    public Test(int x){
        WriteLine("At Bar: "+x);
    }
}
class Baz{
    object TestFn=FuncRef(this, "Test", int\);
    public Test(int x){
        TestFn.Call(x);
    }
    public Test(int x){
        WriteLine("At Baz: "+x);
    }
}

Получается по две Test в каждом. Выполняется всегда последняя. FuncRef тоже берёт ссылку на последнюю. В этом примере первые вообще не используются, а значит метод Test в Foo можно не наследовать, пометив его модификатором exclusive:

public exclusive Test(int x){
    TestFn.Call(x);
}

Так он будет только у Foo. Это сократит время загрузки кода и занимаемую им оперативную память.

Sign In or Register to comment.