Czy istnieje odpowiednik Java słowa kluczowego „yield” w języku C #?

112

Wiem, że w samej Javie nie ma bezpośredniego odpowiednika, ale być może strona trzecia?

To naprawdę wygodne. Obecnie chciałbym zaimplementować iterator, który zwraca wszystkie węzły w drzewie, czyli około pięciu wierszy kodu z wydajnością.

zrywak234
źródło
6
Wiem wiem. Ale myślę, że znajomość większej liczby języków to większa moc. Co więcej, rozwój zaplecza (który robię) w firmie, w której teraz pracuję, odbywa się w Javie, więc tak naprawdę nie mogę wybrać języka :(
ripper234

Odpowiedzi:

91

Dwie opcje znam to Aviad Ben Dov za infomancers-zbiory biblioteki od 2007 i Jim Blackler za YieldAdapter biblioteka z 2008 roku (który jest również wymienione w drugiej odpowiedzi).

Oba pozwolą ci pisać kod z yield returnkonstrukcją podobną do typu w Javie, więc oba spełnią twoje żądanie. Istotne różnice między nimi to:

Mechanika

Biblioteka Aviad używa manipulacji kodem bajtowym, podczas gdy Jim używa wielowątkowości. W zależności od potrzeb każdy może mieć swoje zalety i wady. Prawdopodobnie rozwiązanie Aviad jest szybsze, podczas gdy Jim jest bardziej przenośny (na przykład nie sądzę, aby biblioteka Aviad działała na Androidzie).

Berło

Biblioteka Aviad ma bardziej przejrzysty interfejs - oto przykład:

Iterable<Integer> it = new Yielder<Integer>() {
    @Override protected void yieldNextCore() {
        for (int i = 0; i < 10; i++) {
            yieldReturn(i);
            if (i == 5) yieldBreak();
        }
    }
};

Podczas gdy Jim jest o wiele bardziej skomplikowany, wymaga od ciebie adeptgenerycznego, Collectorktóry ma collect(ResultHandler)metodę ... ugh. Możesz jednak użyć czegoś takiego jak ta otoka wokół kodu Jima autorstwa Zoom Information, co znacznie upraszcza to:

Iterable<Integer> it = new Generator<Integer>() {
    @Override protected void run() {
        for (int i = 0; i < 10; i++) {
            yield(i);
            if (i == 5) return;
        }
    }
};

Licencja

Rozwiązaniem Aviad jest BSD.

Rozwiązanie Jima jest własnością publiczną, podobnie jak jego opakowanie, o którym wspomniano powyżej.

Dąb
źródło
3
Fantastyczna odpowiedź. Nie tylko całkowicie odpowiedziałeś na pytanie, ale zrobiłeś to w bardzo jasny sposób. Podoba mi się również format Twojej odpowiedzi i sposób dołączenia informacji o licencji. Kontynuuj niesamowite odpowiedzi! :)
Malcolm,
1
Nie zapomnij o guawie AbstractIterator.
shmosel
Właśnie opublikowałem tutaj kolejne (na licencji MIT) rozwiązanie, które uruchamia osobny wątek dla producenta i ustawia ograniczoną kolejkę między producentem a konsumentem: github.com/lukehutch/Producer
Luke Hutchison
Zauważ, że yield(i)zepsuje się od JDK 13, ponieważ yieldjest dodawany jako nowa instrukcja Java / zastrzeżone słowo kluczowe.
Luke Hutchison
14

Oba te podejścia można uczynić nieco czystszymi, teraz Java ma Lambdy. Możesz zrobić coś takiego

public Yielderable<Integer> oneToFive() {
    return yield -> {
        for (int i = 1; i < 10; i++) {
            if (i == 6) yield.breaking();
            yield.returning(i);
        }
    };
}

Trochę więcej wyjaśniłem tutaj.

benjiweber
źródło
4
Yielderable? Czy nie powinno tak być Yieldable? (czasownik to po prostu „ustąpić”, a nie „ustąpić”, „ustąpić” czy cokolwiek)
Jochem Kuijpers
yield -> { ... }zepsuje się od JDK 13, ponieważ yieldjest dodawany jako nowa instrukcja Java / zastrzeżone słowo kluczowe.
Luke Hutchison
1

Wiem, że to bardzo stare pytanie i są dwa sposoby opisane powyżej:

  • manipulacja kodem bajtowym, która nie jest taka łatwa podczas przenoszenia;
  • oparte na wątkach, yieldktóre oczywiście wiążą się z kosztami zasobów.

Istnieje jednak inny, trzeci i prawdopodobnie najbardziej naturalny sposób implementacji yieldgeneratora w Javie, który jest najbliższą implementacją do tego, co kompilatory C # 2.0+ robią dla yield return/breakgeneracji: lombok-pg . Jest w pełni oparty na automacie stanowym i wymaga ścisłej współpracy z javackodem źródłowym AST. Niestety, wydaje się, że obsługa lombok-pg została przerwana (brak aktywności repozytorium przez ponad rok lub dwa), a oryginalny Project Lombok niestety nie ma tej yieldfunkcji (ma lepsze IDE, takie jak Eclipse, wsparcie IntelliJ IDEA).

Lyubomyr Shaydariv
źródło
1

Stream.iterate (seed, seedOperator) .limit (n) .foreach (akcja) to nie to samo, co operator yield, ale może być przydatne napisanie własnych generatorów w ten sposób:

import java.util.stream.Stream;
public class Test01 {
    private static void myFoo(int someVar){
        //do some work
        System.out.println(someVar);
    }
    private static void myFoo2(){
        //do some work
        System.out.println("some work");
    }
    public static void main(String[] args) {
        Stream.iterate(1, x -> x + 1).limit(15).forEach(Test01::myFoo);     //var1
        Stream.iterate(1, x -> x + 1).limit(10).forEach(item -> myFoo2());  //var2
    }
}
Andrey Lavrukhin
źródło
0

Sugerowałbym również, jeśli już używasz RXJava w swoim projekcie, aby użyć Observable jako „yieldera”. Można go użyć w podobny sposób, jeśli stworzysz własny Observable.

public class Example extends Observable<String> {

    public static void main(String[] args) {
        new Example().blockingSubscribe(System.out::println); // "a", "b", "c", "d"
    }

    @Override
    protected void subscribeActual(Observer<? super String> observer) {
        observer.onNext("a"); // yield
        observer.onNext("b"); // yield
        observer.onNext("c"); // yield
        observer.onNext("d"); // yield
        observer.onComplete(); // finish
    }
}

Observables można przekształcić w iteratory, dzięki czemu można ich używać nawet w bardziej tradycyjnych pętlach for. Również RXJava zapewnia naprawdę potężne narzędzia, ale jeśli potrzebujesz tylko czegoś prostego, może to byłaby przesada.

Győri Sándor
źródło
0

Właśnie opublikowano kolejną (MIT licencjonowanych) roztworu tutaj , otwiera się do producenta w oddzielnym wątku oraz określa ograniczonym kolejki pomiędzy producenta i konsumenta, dzięki czemu do buforowania, do kontroli przepływu i równoległe przetwarzanie potokowe od producenta i konsumenta (tak że konsument może pracować nad skonsumowaniem poprzedniego przedmiotu, podczas gdy producent pracuje nad wyprodukowaniem następnego).

Możesz użyć tego anonimowego formularza klasy wewnętrznej:

Iterable<T> iterable = new Producer<T>(queueSize) {
    @Override
    public void producer() {
        produce(someT);
    }
};

na przykład:

for (Integer item : new Producer<Integer>(/* queueSize = */ 5) {
    @Override
    public void producer() {
        for (int i = 0; i < 20; i++) {
            System.out.println("Producing " + i);
            produce(i);
        }
        System.out.println("Producer exiting");
    }
}) {
    System.out.println("  Consuming " + item);
    Thread.sleep(200);
}

Lub możesz użyć notacji lambda, aby zmniejszyć wartość domyślną:

for (Integer item : new Producer<Integer>(/* queueSize = */ 5, producer -> {
    for (int i = 0; i < 20; i++) {
        System.out.println("Producing " + i);
        producer.produce(i);
    }
    System.out.println("Producer exiting");
})) {
    System.out.println("  Consuming " + item);
    Thread.sleep(200);
}
Luke Hutchison
źródło