Laden...

Forenbeiträge von Palladin007 Ingesamt 2.080 Beiträge

09.02.2023 - 09:51 Uhr

Wirklich helfen kann ich nicht, aber dieses Forum hier läuft in der Azure-Cloud, hier ein Artikel dazu:
[Artikel] Die myCSharp.de Infrastruktur
Ist vielleicht ein Blick wert?

08.02.2023 - 18:44 Uhr

Dafür baut man Abstraktionen, Du brauchst eine Abstraktion, die die direkte Arbeit mit der Internetverbindung übernimmt.
Im Produktivcode verwendest Du dann eine produktive Implementierung, für UnitTests eine Version, die das zurückgibt, was der Test braucht.

02.02.2023 - 19:10 Uhr

Schau dir meinen Link an.
Oder lies dich am besten direkt in das ganze Kapitel zur Shell ein.
Ich musste auch erst ein bisschen damit herumspielen, bevor ich einen Überblick hatte ^^

Du definierst die Inhalte der Shell und damit indirekt auch den Aufbau der Anwendung, also z.B. Tabs oder Flyout-Inhalte.
Die Navigation arbeitet dann mit Routen, ein bisschen vergleichbar mit der Navigation in einer Web-Anwendung.

Du definierst die Inhalte und Routen und wie das alles zusammenhängt und rufst die Routen auf, um zu navigieren.

02.02.2023 - 12:00 Uhr

So ein Popup ist ja auch nicht viel anders, als sowas:


<Grid>
    <Content />

    <Popup IsVisible="..." Background="...">
        <PopupContent Margin="..." />
    </Popup>
</Grid>

Also der PageContent, dank einem Grid über dem aktuellen Conten + Margin und irgendein halbtransparenter Hintergrund. Kann man sich also auch wunderbar selber bauen.
Oder das Community Toolkit, damit macht man eigentlich nicht viel falsch.

Aber ich stimme Bernd zu, ich wüsste auf Anhieb keine App, die das so macht 😁
Außerdem kann die Shell dann die ganze Navigation übernehmen: .NET MAUI Shell navigation - .NET MAUI

23.01.2023 - 18:25 Uhr

@Abt:
Soweit ich weiß sind die genannten Build-Funktionen für .NET 4.8 nicht verfügbar, oder?
Demnach fällt hier auch der Single File Build raus, da es hier ja um .NET 4.8 geht.

23.01.2023 - 17:01 Uhr

Ich habe nur ganz wenige, kleine Resourcen eingebettet. Das sind ein paar Icons und ein paar Fonts - das war's.

Wenn es keine Resourcen sind, ist es etwas anderes, aber das kannst nur Du sehen.
Vielleicht hast Du nicht alle Resourcen auf dem Schirm? Dekompiliere doch mal mit ILSpy und schau dir an, was der dir für Resourcen anzeigt.
Oder natürlich irgendwelche schrägen Einstellungen.

Ohne mehr Infos führt das hier aber zu nichts.
Wirf doch mal so lange Inhalte raus, bis Du einen deutlichen Größenunterschied sehen kannst.
Dann kennst Du die Ursache oder zumindest einen Teil davon.

Mein kleines Testgerät hier wollte die - zum Glück testhalber schnell erstellte - .net Anwendung nicht starten.

.NET muss natürlich installiert sein, alt und neu sind nicht kompatibel - hast Du das bedacht?
Wenn das Installieren von .NET nicht in Frage kommt, kannst Du mit der "self-contained"-Option auch Runtime + Framework mit liefern, dann wird das Ergebnis natürlich deutlich größer, aber mit anderen Funktionen kann man da wieder viel reduzieren.

Application publishing - .NET
Installieren von .NET unter Windows - .NET
core/supported-os.md at main · dotnet/core (Und die anderen Versionen - schau im Pfad)

23.01.2023 - 15:53 Uhr

Ich hab gerade eine WPF Anwendung mit 4.8 und Microsoft.Xaml.Behaviors, Prism.WPF und Prism Core erstellt, sonst nichts.
Der Debug-Build ist unter 2 MB groß.

Das Problem liegt also in deinem eigenen Code, hast Du große Dateien als embedded Resource, ein Haufen Bilder oder besondere Einstellungen?

Und Du solltest auf das neue .NET umsteigen - macht vieles deutlich einfacher.
Z.B. ist die csproj-Datei deutlich übersichtlicher.

15.01.2023 - 20:02 Uhr

Oder es ist ein Fehler im Code

Und auch in der Frage?
Die Frage ist ja gerade, warum die vorherigen Tasks nicht abbrechen.
im Umkehrschluss heißt das, die Tasks sollen abbrechen.

Aber natürlich ist das alles ohne mehr Infos nur Spekulation.

Lieber eine Lösung, die funtioniert

Wieso sollte das nicht funktionieren?
Gut, dieser Code hat seine Macken, aber das Konzept an sich sollte funktionieren.

Davon abgesehen gibts eine fast fertige Lösung in den Docs: TaskScheduler Class

Mich stört nicht, dass ich (wenn es mein Projekt wäre) mich da einarbeiten müsste, sondern dass ich das dann auch von meinen Kollegen verlange, sobald sie an der Stelle arbeiten.
Für mich wäre es schon deshalb eine Option, weil es interessant ist, aber wenn später jemand damit arbeitet und nicht so viel Interesse zeigt, ist diese Klasse ein Fehler-Risiko, das man mit einer einfacheren Implementierung reduzieren oder sogar verhindern könnte.

15.01.2023 - 19:41 Uhr

Die Channel würden zwar nur einen Task zulassen, aber weitere Aufgaben würden warten, oder?
Im Test-Code soll aber der jeweils laufende Task abgebrochen werden, daher schließe ich darauf, dass das auch produktiv so sein soll.

Und ein eigener TaskScheduler klingt ziemlich overkill.
Ich würde - wenn der jeweils laufende Task abgebrochen werden soll - bei dem simplen Ansatz aus dem Test-Code mit der CancellationTokenSource bleiben.
Es erfüllt seinen Zweck und ich leicht zu verstehen und potentiell nötige Architektur-Umbauten lassen sich mit einer Abstraktion zumindest offen halten.

Hängt natürlich immer vom konkreten Ziel ab, aber solange die Anforderungen es nicht erzwingen, würde ich auf eher schwierigere Lösungen (viele haben ja schon Schwierigkeiten mit Tasks an sich) verzichten.

14.01.2023 - 22:36 Uhr

Das Geheimnis ist der CancellationToken - den gibt's nicht ohne Grund 😉
Jeder Task muss seinen Token für sich habe, dein Code prüft aber immer die jeweils neuste CancellationTokenSource und die ist nie abgebrochen, weil sie immer sofort ersetzt wird.

14.01.2023 - 17:03 Uhr

Ich kenne MEF nicht, daher kann ich keine Alternative empfehlen.
Ich habe bisher aber nichts derartiges vermisst, eine modulare Architektur würde ich mit Hilfe der "Microsoft.Extensions.***"-Frameworks aufbauen, hauptsächlich das DependencyInjection-Framework für die generelle Architektur und weil man ziemlich simpel Services per Reflection nachladen, registrieren und so in der Anwendung verteilen kann.
Eine geeignete Architektur musst Du dann aber immer noch selber entwerfen.

Ist das Entity Framework immer noch State of the Art oder gibt es dafür auch bereits einen Nachfolger, der inzwischen genutzt wird?

Ja und Ja
Das Entity Framework wurde neu entwickelt, heißt aber immer noch Entity Framework - meist gefolgt von einem "Core", also "Entity Framework Core".
Das kannst Du bedenkenlos verwenden.

12.01.2023 - 14:15 Uhr

Also wäre es vielleicht so gemeint, dass sich HTML-Technologien verbreiten, weil viel in Richtung Multiplatform geht, aber reine Desktop-Anwendungen betrifft das nicht, sie werden nur weiter zurückgehen.

So habe ich das gemeint, ja.
Die größte Zielgruppe arbeitet an betrieblichen Produkten, die sind natürlich daran interessiert, viele Menschen zu erreichen und das geht am besten, wenn es überall läuft.

Gibt es überhaupt etwas, das mit MAUI-Blazor gar nicht geht?

Mit MAUI Blazor habe ich noch keine Erfahrung, aber MAUI ohne Blazor hat noch einige Probleme.
Ich sehe da viel Potential, aber es bleibt abzuwarten, wie es sich entwickelt.

Avalonia platform support - why it's simple

Ich arbeite seit bald einem Jahr mit Avalonia und ich bin so gar nicht begeistert davon.
Ja, es läuft auf vielen Geräten und ist recht nahe an WPF, aber es wirkt auf mich an einigen Stellen nicht ganz zuende gedacht, ich habe z.B. immer wieder Probleme mit den Styles.
Man kann es natürlich ausprobieren, es ist nicht pauschal schlecht, aber solange ich die Wahl habe, werde ich es nicht mehr nutzen.

12.01.2023 - 10:30 Uhr

Welchen Vorteil hätte Blazor denn für eine reine Desktop-Anwendung gegenüber WinUI?

Keinen, außer so allgemeine Dinge, wie dass Du Web-KnowHow nutzen kannst.

Viel spannender ist, dass Du ein Projekt mit einer kompletten Code-Basis aufbauen kannst, die auf dem Desktop, als Website oder Handy-App laufen kann.
Wenn Du nur ein klassisches Desktop-Programm haben willst, würde ich kein Blazor verwenden - außer Du hast viel Erfahrung mit HTML/CSS.
Für viele Firmen kann das aber ein entscheidender Vorteil sein, so reicht ein Team, das sich auf einen begrenzten Technologie-Stack spezialisieren kann und das kann alle Ziel-Plattformen bedienen.

Ich würde es von den technischen Begebenheiten abhängig machen, also welche Nachteile Blazor auf dem Desktop hat (weiß ich nicht) und das mit deinen Anforderungen abgleichen.
Und Du solltest auch deine Fähigkeiten und die Zukunftsplanung mit einbeziehen, also wenn es mal als Website laufen soll, bietet sich natürlich Blazor an.

11.01.2023 - 13:55 Uhr

Guter Gedanke

Bei WebView2 muss die Engine installiert sein, aber das dürfte zumindest auf den Privat-Rechnern überall der Fall sein, schätze ich.
Das Browser Control ist natürlich deutlich überdimensioniert für das Ziel, aber sollte funktionieren.

10.01.2023 - 20:33 Uhr

Gibt es ein PDF-Control das Free ist und einigermaßen gut aussieht ?

Befrag eine Suchmaschine deiner Wahl nach "c# pdf viewer", ich kenne nichts.

2x Prozess aufrufen funktioniert leider nicht.

Dann macht dir wohl der Adobe Reader einen Strich durch die Rechnung, ich schätze mal, er möchte die PDF im selben Fenster öffnen, stellt fest, dass es die selbe Datei ist und öffnet einfach nur den bereits offenen Tab.

10.01.2023 - 19:20 Uhr

Das öffnet das Standard-Programm, in meinem Fall wäre das mein Browser inklusive offener Tabs.
Natürlich kannst Du einen Prozess auch einfach zwei Mal starten, aber es kann auch sein, dass das Programm das verhindert, Browser und der Adobe Reader haben da besonderes handling, das musst Du austesten. Wenn die dir da einen Strich durch die Rechnung machen, hast Du jedenfalls keine Chance.

Aber Du kannst das Fenster positionieren, allerdings geht das nur über die WinAPI.
Du brauchst das WindowHandle (der Prozess kann aber auch mehrere Fenster haben), dann kannst Du mit EnumDisplayMonitors die Monitore abrufen, mit GetMonitorInfo Details über den Monitor abrufen und mit SetWindowPos das Window positionieren.

Oder Du schaust dich nach einem PDF-Control um und erstellst die Fenster selber, dann hast Du mehr Möglichkeiten und kannst auch beliebig viele Fenster öffnen.
Empfehlen kann ich dazu aber nichts.

06.01.2023 - 19:41 Uhr

Ein Namespace darf nicht mit einer Zahl beginnen.

04.01.2023 - 15:39 Uhr

Zu der Frage nach dem "Wie" kann ich nichts mehr sagen, ich stimme Abt und Th69 zu.

Aber etwas, was man mMn. immer auf im Hinterkopf haben sollte: S.O.L.I.D
Das sind fünf Prinzipien, die nicht unbedingt einfach zu verstehen aber sehr wichtig sind.
Es gibt aber keine Anleitung, was man wie machen sollte und dann wird alles gut, es sind stattdessen sehr abstrakt formulierte Prinzipien, von denen man die Gründe und die Ziele verstehen muss, nur dann kann man sie auch richtig anwenden.

Am besten behält man sie immer im Hinterkopf und liest sich da alle paar Monate/Jahre neu ein, um sein Verständnis mit den neu gemachten Erfahrungen aufzufrischen.
Ansonsten kann ich noch das Buch "Clean Architecture" von Robert C. Martin (Uncle Bob) empfehlen, da sind unter Anderem die mMn. besten Erklärungen für die SOLID-Prinzipien enthalten. Oder in Video-Form eine Reihe von David Tielke auf YouTube, dessen Erklärungen ich auch gut finde - wenn auch nicht am besten: SOLID Principles von Robert C. Martin (Uncle Bob)

PS:
Und M.L. war schneller 🙂

02.01.2023 - 02:22 Uhr

Man muss viele Sachen manuell machen. Bei verschiedenen Auflösungen des Displays, kommt bei Handys dauernd vor, muss man Buttongröße und Schriftgröße selber per Programm anpassen.

Meiner Erfahrung nach ist das, was Du beschreibst, ein selbst gemachtes Problem 😉
Das Standard-Verhalten reicht völlig aus, um responsive Oberflächen zu entwerfen, erst bei einer zu sehr abweichender Oberfläche (z.B. Menü-Zeile vs. Menü-Flyout) geht es natürlich nicht mehr automatisch.
Z.B. bietet das Grid ganz einfache Möglichkeiten, Position und Größe der Inhalte prozentual festzulegen, in Verbindung mit dem Margin, MinWidth oder MaxWidth und Alignments erreicht man schon sehr viel.

Allerdings geht es bei der Frage, was nun einfacher ist, nicht darum, wie gut existierende Controls implementiert wurden (WPF, Xamarin, MAUI, etc. sind ja alle sehr flexibel), sondern um die Syntax und die grundlegenden Konzepte, wie Styling oder Templates, bzw. wie leicht das alles zu erlernen ist.

Einige Controls sind nicht brauchbar

In jedem UI-Framework gibt's Komponenten, die nicht so dolle sind 😁
Ich hatte zuletzt erst mit file-Input in einem Web-Projekt Probleme und bei einem Avalonia-Projekt passe ich ständig Default-Templates an.

30.12.2022 - 15:14 Uhr

Ohne einen Designer mit WYSIWYG dauert das Ganze halt nunmal um ein Zigfaches länger, selbst wenn man eingearbeitet ist.

Ich bestätige Abts Einschätzung.
Ich ziehe XAML jederzeit einem Editor vor, einfach weil ich 1. viel schneller bin und 2. besser steuern kann, was ich haben will.
Es ist alles eine Frage der Übung und mit der Zeit hast Du schon eine ziemlich klare Vorstellung, wie der Code aussieht, wenn Du es startest.
Aber das dauert natürlich.

im Gegensatz zu eben HTML die Lernhürde super hoch

Dem kann ich aber nicht zustimmen.
Ob nun XAML oder HTML einfacher ist, wage ich nicht zu beurteilen, aber CSS oder häufig auch JavaScript machen es für mich furchtbar. Aber Blazor beseitigt zumindest JavaScript zu einem großen Teil aus der Gleichung.
Ich muss aber auch sagen, dass ich mit WPF und XAML "aufgewachsen" bin und HTML/CSS erst später dazu kam, mit anderem Werdegang würde ich das vermutlich andersherum sehen.

30.12.2022 - 15:00 Uhr

Die CPU unterstützt bis zu 16 GB RAM:
Intel Core i55257U Processor 3M Cache up to 3.10 GHz Product Specifications
Dann müsste es das Notebook eigentlich auch schaffen.

Aber ob das wirklich viel bringt, kann ich nicht sagen.
Was bei dir die die Haupt-Ursache ist, musst Du herausfinden, der TaskManager kann da bestimmt mer Auskunft geben.
Du kannst natürlich erst einmal auf 16 GB aufrüsten und gucken, wie viel es bringt, aber wenn nicht, wäre das Geld in den Sand gesetzt.

Am besten, Du kaufst einen Desktop-PC, das ist meist günstiger und Du hast flexiblere Möglichkeiten aufzurüsten.

Und wenn Du alles für Unterwegs auch auf dem Notebook brauchst:
Besorge dir eine online Quellcodeverwaltung (oder ein eigener Git-Server, z.B. auf einem RaspberryPi) und die Visual Studio Einstellungen kannst Du über den Account verteilen lassen.

29.12.2022 - 13:26 Uhr

Das compilen dauert nicht so lange, sondern das starten des Emulators und das installieren darauf
Wenn Du den Emulator offen lässt, geht das bedeutend schneller, dauert aber natürlich immer noch seine Zeit.
Ansonsten gibt's noch HotReload, das funktioniert im begrenzten Maße ganz gut, dann kannst Du die UI (ich hab's nur mit XAML getestet) live ändern und direkt sehen, wie es aussieht.

Natürlich kann auch dein Rechner zu langsam sein.
Deine CPU hat schon einige Jahre auf dem Buckel und nur zwei Kerne (4 Threads), das mit nur 8GB RAM ist ziemlich sicher zu langsam.
Schau doch mal, wie stark die vier Threads und der RAM ausgelastet sind, VS2022 braucht recht viel, wenn dein RAM voll ist, lagert Windows auf die SSD aus und das merkst Du dann sehr deutlich.

Ist das dafür wirklich so wenig ausreichend, oder geht es einfach nicht schneller bis die App auf dem Emulator sichtbar wird?

Beides und bedenke: Dein altersschwacher Rechner muss ja auch den Emulator am Laufen halten.

Würde hier mehr CPU Power oder RAM helfen?

Das kann man beinahe immer mit "Ja" beantworten 😉

29.12.2022 - 13:16 Uhr

Fertige UIs wirst Du nicht bekommen.
Du kannst dich aber mal bei den großen Anbietern (z.B. DevExpress) umschauen, ob die schon was für MAUI haben.

Ansonsten:
FontAwesome und .NET MAUI Community Toolkit

Darüber hinaus solltest dir Du aber lieber die Zeit nehmen und Erfahrung mit MAUI sammeln.
Mit der Zeit wird's einfacher und so hast Du dann alle Möglichkeiten offen.
Und wenn Du nur mit XAML Schwierigkeiten hast, gibt's auch C#-Markup, ich persönlich finde XAML aber übersichtlicher.

24.12.2022 - 15:40 Uhr

Ich hab mir deine Frage nochmal durchgelesen.

Du hast einen Service per Dependency Injection, den es nur auf Mobile-Geräten gibt?
Also beim Aufbau des Containers wird dieser Service nur für Mobile-Geräte registriert?

Was hinter dich daran, diesen Service auch für alle anderen Geräte zu registrieren, zur Not eine Dummy-Implementierung, die nichts tut? Stichwort Null object pattern
Oder Du rufst den Service optional ab, das geht über den Constructor (Parameter muss optional sein), mit Inject weiß ich nicht.
Oder Du registrierst eine Func<IMyService>, die den Service abruft und die Func rufst Du nur für Mobile-Geräte ab.

JavaScript brauchst Du dafür ganz sicher nicht.

24.12.2022 - 09:13 Uhr

..., deshalb ein Enum für sowas 😉

In C# werden Dependencies meistens über den Klassen Konstruktor als Parameter injected

Nicht in Blazor - nicht nur zumindest.
Da gibt es das InjectAttribute, was Property injection aktiviert.
Constructor injection ist bei Komponenten standardmäßig nicht möglich.
Ändern kann man das, indem man seinen eigenen IComponentenActivator erstellt und im Container registriert, der Default ist ziemlich simpel: DefaultComponentActivator. Einfach einen IServiceContainer im Konstruktor entgegen nehmen und anstelle des Activators die ActivatorUtilities nutzen.

Wie das bei MAUI mit Blazor aussieht, weiß ich nicht, aber vermutlich genauso.

23.12.2022 - 23:11 Uhr

komplett für Menschen schwer zu lesener Code

Der ist nur unformatiert, ist immer noch gutes altes HTML

der die gesuchten Daten (die Weltranglistenpunkte) nicht mal enthält

Doch - Zeile 220 (zumindest bei dir)
Fängt mit figure und hat als erstes Child eine table.
Da stehen (vermutlich - hab's nicht abgeglichen) alle Daten drin.
Leider keine id, damit wäre es leicht gewesen, die Daten zu finden.

23.12.2022 - 21:41 Uhr

Da die Webseite der FIFA selber aber nicht geeignet ist, weil der Seitensourcecode erst im Browser gerendert werden muss

Kann ich so nicht bestätigen.
Ich kann deine URL einfach als curl-command absetzen und bekomme alles zurück - inklusive der Daten.

Aber wenn Du eine vollständige Website hast, kannst Du dir auch das HTML Agility Pack anschauen.
Das kann CSS-Selektoren, dabei hilft dir dann der Browser, um den richtigen Selector zu finden.

23.12.2022 - 04:09 Uhr

Glaub mir: XPath ist einfacher.

Und du kannst auch das XML auf eine Klasse mappen oder Du liest es Manuell Element für Element.

Was nun die bessere Option ist, kann ich nicht sagen, ohne zu wissen, wo die Reise hin gehen soll.

23.12.2022 - 01:33 Uhr

Wäre es dann nicht besser, den Fehler mit XML zu suchen, anstatt auf Regex zurückzugreifen?
Weil mit XML oder XPath geht es ganz sicher - Du hast also irgendwas falsch gemacht.

Wenn ich das z.B. in Notepad++ werfe, ein root-Element drum (damit es ein Document ist), kann ich problemlos alle Werte abrufen:

22.12.2022 - 22:48 Uhr

Du könntest auch einfach nach "</tr>" splitten, dürfte bedeutend einfacher sein:


var text = "<tr><td>1.</td><td>&#x1f1e7;&#x1f1f7; Brasilien</td><td>1840.77</td><td>&#x2194;&#xfe0f;</td></tr><tr><td>2.</td><td>&#x1f1e6;&#x1f1f7; Argentinien</td><td>1838.38</td><td>&#x1f51d;</td></tr><tr><td>3.</td><td>&#x1f1eb;&#x1f1f7; Frankreich</td><td>1823.39</td><td>&#x1f51d;</td></tr><tr><td>4.</td><td>&#x1f1e7;&#x1f1ea; Belgien</td><td>1781.3</td><td>&#x1f53b;</td></tr><tr><td>5.</td><td>&#x1f3f4;&#xe0067;&#xe0062;&#xe0065;&#xe006e;&#xe0067;&#xe007f; England</td><td>1774.19</td><td>&#x2194;&#xfe0f;</td></tr><tr><td>6.</td><td>&#x1f1f3;&#x1f1f1; Niederlande</td><td>1740.92</td><td>&#x1f51d;</td></tr>";

foreach (var part in text.Split("</tr>", StringSplitOptions.RemoveEmptyEntries))
    Console.WriteLine(part + "</tr>");

Oder Du parst es als XML - HTML ist ja auch nix anderes als XML.
Damit hättest Du dann mit Abstand die meisten Möglichkeiten.

06.12.2022 - 22:49 Uhr

Das haste aber bei jeder Remote Technologie

Natürlich, deshalb sollte der Sender ja auch nicht mehr machen können, als er braucht.
Also kein Shell-Zugriff, sondern nur passgenau definierte Funktionen, die gar keinen Spielraum bieten.

06.12.2022 - 22:28 Uhr

sondern eher dass der Sender des Befehls auch entsprechend authentifiziert und autorisiert ist

Es bleibt dennoch ein enormes Sicherheitsrisiko.
Wie stellst Du sicher, dass der Sender nicht unter fremder Kontrolle steht?

Du solltest immer darauf achten, dass dein Server abgesichert ist und wenn Du einer externen Instanz erlaubst, beliebigen Code auszuführen, öffnest Du Tür und Tor für Angreifer.
Besser wäre es, wenn man die konkreten Anwendungsfälle mit entsprechenden Parametern in genau dem Rahmen implementiert, den man braucht, nicht mehr, nicht weniger.
Wenn der Sender dann unter fremder Kontrolle steht, kann der zwar unerlaubt auf die API zugreifen, aber auf dem Server hat er maximal den Einfluss, den die implementierten Funktionen erlauben.

Lässt Du stattdessen frei Zugriff auf die Shell zu, könnte der Angreifer absolut alles machen.

06.12.2022 - 15:37 Uhr

ASP.NET

Ein Programm, dass eine HTTP-Nachricht direkt in einer Shell ausführt, wäre allerdings ein krasses Sicherheitsrisiko.

24.11.2022 - 20:57 Uhr

Laut Communityeinstellungen sollte ich nicht über jede neue PN benachrichtigt werden, aber ich bekomme trotzdem jedes Mal eine Email?

24.11.2022 - 17:30 Uhr

Aber gerade so Methoden wie DoSomethingSpecial sollten doch besser mit OOP gelöst werden, d.h. eine abstrakte (bzw. virtuelle) Methode, welche dann aufgerufen wird.

... oder ein Interface mit dieser Methode, das man dann nach Bedarf implementiert oder eben nicht.

24.11.2022 - 17:04 Uhr

Der Unterschied ist, dass die Generics bei C# im Gegensatz zu Java "zuende" implementiert sind.
Bei Java ist der Datentyp der generischen Variablen, Parameter, etc. immer noch object und der Compiler passt zusätzlich auf.
Bei C# gehen die Generics bis runter auf die IL-Ebene (das Gegenstück zum Java ByteCode) und sind sowohl in den Metdadaten, in den Binaries und zur Laufzeit existent.
Dadurch muss/kann (ich empfinde das als riesigen Vorteil) C# bei dem Thema aber auch deutlich strenger sein und erlaubt solche Casts nicht.

Wenn wir nun dein Tier-Beispiel betrachten:


List<List<Tier>> lists = new List<List<Tier>>();

// Angenommen, das hier wäre erlaubt, ...
lists.Add(new List<Katze>());
lists.Add(new List<Hund>());

List<Tier> katzen = lists[0];
List<Tier> hunde = lists[1];

// ... was passiert dann hier?
// Eigentlich müsste es doch erlaubt sein, da die Variable ja den Typ List<Tier> hat.
// Aber was passiert zur Laufzeit, wenn sich herausstellt, dass die Instanz einen nicht passenden Typ hat?
katzen.Add(new Hund());
hunde.Add(new Katze());

Die inneren Listen sind vom Typ Tier, demnach müsste man ja auch jede Tier-Instanz hinzufügen dürfen, oder?
Wenn die konkrete innere Liste aber vom Typ Katze ist, wie soll da dann Hund hinzugefügt werden?
Diese Einschränkung ist also durchaus sinnvoll, sie verhindert Cast-Fehler zur Laufzeit.

Folgendes geht allerdings schon:


List<IReadOnlyList<Tier>> lists = new List<IReadOnlyList<Tier>>();
lists.Add(new List<Katze>());
lists.Add(new List<Hund>());

Oder Du bleibst beim Basis-Typ Tier:


List<IReadOnlyList<Tier>> lists = new List<IReadOnlyList<Tier>>();
lists.Add(new List<Tier>());
lists.Add(new List<Tier>());

// Hier geht alles, Du musst nur jedes Mal prüfen, ob Du Hund oder Katze hast.
// Meiner Erfahrung nach deutet aber genau das (dass man immer auf den konkreten Typ prüfen muss) auf grobe Architektur-Fehler an anderer Stelle.

Das geht, weil der Datentyp IReadOnlyList<T> den generischen Datentyp nur "raus gibt", da kann Katze oder Hund natürlich immer nach Tier gecastet werden.
IReadOnlyList<T> kann aber nicht verändert werden, der generische Datentyp kann nicht "rein gegeben" werden, denn da hättest Du wieder das Problem, dass Tier nicht immer nach Katze oder Hund gecastet werden kann.

Das Thema heißt Covariance and Contravariance in Generics

24.11.2022 - 13:26 Uhr

Da habe ich den Wald vor lauter Bäumen nicht gesehen!

Ich glaube, da haben wir alle den Wald vor lauter Bäumen nicht gesehen - außer Th69 😁

Würde es deiner Meinung nach eine bessere Alternative geben dies zu lösen? Ich bin für alles Offen und lasse mich gerne auf andere Ideen/Möglichkeiten bringen.

Besser wäre, wenn Du auf das wilde Format verzichten könntest 😁

Aber wenn das nicht geht, ist das, was Du tust, die einzig sinnvolle Lösung.
Das kann man natürlich noch beliebig weit ausbauen, aber da bleibt natürlich die Frage, was Du brauchst und was nicht.

Und das Casten/Parsen würde ich weiterhin explizit machen, so hast Du einfach mehr Kontrolle darüber und mit dem IParsable-Interface dürfte ein Großteil der Arbeit auf einen Schlag erledigt sein.

24.11.2022 - 10:49 Uhr

C# ist kein JavaScript, man kann einen String nicht in ein int casten.

Die Convert.ChangeType-Methode versucht stattdessen zu parsen, aber zuverlässig ist das auch nicht.
Ich würde in so einem Fall lieber einzeln auf jeden Datentyp prüfen und je die richtige Parse-Methode nutzen.
Convert.ChangeType ist dann nur noch die letzte Alternative.

Mit .NET 7 gibt's auch ein ein IParsable-Interface, was das deutlich einfacher machen sollte.

Abgesehen davon:
Was für ein Dateiformat hast Du? CSV?
Wenn es ein bekanntes Format ist, dann greife lieber auf bestehende Frameworks zurück.

23.11.2022 - 23:32 Uhr

Spannend ist: Was ist das Ziel?
Dateipfade 100000fach aneinander ketten klingt jedenfalls ziemlich sinnlos.

Wenn das doch dein Ziel sein soll, dann nimm eine Liste oder, wenn Du die Anzahl vorher kennst, ein Array, das dürfte bei der Größe nochmal ein wenig schneller sein.
Da wirfst Du dann jeden Pfad rein und string.Concat verkettet die dann alle.


string[] array = new string[Liste.Count];
for (int i = 0; i < Liste.Count; i++)
{
    array[i] = Liste[i].Path;
}
string a = string.Concat(array);

Ist immer noch nicht optimal, weil ein riesiger String erstellt werden muss, was natürlich auch Zeit und Platz benötigt, aber sollte schneller sein.
Und wenn Du z.B. Duplikate suchen willst, hilft ein HashSet.

Aber besser wäre natürlich, wenn Du das tatsächliche Ziel entsprechend optimierst.

16.11.2022 - 13:11 Uhr

Der Setter wird von WPF nicht aufgerufen, das passiert im Hintergrund, deshalb gibt es ja den Callback.
Aber natürlich ist die Geschweifte Klammer hinter dem SetValue trotzdem falsch.

Und wie man denn Callback nutzt, beantwortet eine simple Google-Suche, führt mich dann zu:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.propertychangedcallback?view=windowsdesktop-7.0

Abgesehen davon:


this.Value = this.Value * FactorFloat;

Wenn das auch passieren soll, wenn Value sich ändert, dann hast Du eine Endlosschleife - dein Vorhaben funktioniert also nicht.

Und nochmal:
Deine Fragen sind absolute Basics von den Grundlagen.
Du wirst mit WPF nur verzweifeln, wenn Du dich nicht erst auf die Grundlagen konzentrierst.

16.11.2022 - 12:54 Uhr

Ich weiß nicht, ob es an der Min-.NET-Version hängt, aber ich bezweifle es.
Ich schätze eher, es hängt am csproj-Format, dass Du das neue SDK-Format brauchst.
Man kann das alte ASP.NET auf das neue SDK-Format umstellen, ich hab das vor einiger Zeit mal "per Hand" gemacht - einen einfachen Weg kenne ich aber nicht.

Ansonsten kannst Du auch manuell die Dateien zuordnen, das geht in der csproj:


<ItemGroup>
  <Compile Include="a.cs" />
  <Compile Include="b.cs">
    <DependentUpon>a.cs</DependentUpon>
  </Compile>
</ItemGroup>

Dann wird die a.cs unter der b.cs angezeigt.

Besser wäre natürlich, Du steigst auf das neue ASP.NET um, das bringt noch einiges mehr an Vorteilen 😉

16.11.2022 - 10:10 Uhr

Die PropertyMetadata-Klassen haben einen PropertyChangedCallback-Parameter, da übergibst Du eine Methode.
Die Methode wird dann von WPF aufgerufen, wenn sich was geändert hat, dann kannst Du die neuen Werte multiplizieren und der Value-Property zuweisen.

Bin was C# angeht absoluter Anfänger.

Dann solltest Du nicht mit WPF starten, auch nicht mit WinForms oder Unity.
Fang mit einfachen Konsolen-Programmen an, bis die C#-Grundlagen sitzen.

04.11.2022 - 14:08 Uhr

Was Du siehst, sind vermutlich Folgefehler, weil er nicht kompilieren kann oder die XAML-Datei einen Fehler enthält.
Schau mal in der Errors-Liste oder im Build-Output (Die Extension "Output Enhancer" macht's übersichtlicher), ob Du da einen anderen Fehler findest.
Und mach ein Rebuild, keinen einfachen Build, eventuell hat sich was aufgehängt.

03.11.2022 - 22:48 Uhr

Forme das doch mal Schritt für Schritt um:


if (File.Exists(nameBackup) == true || File.Exists(nameBackup) == false) { /* code */ }


if (File.Exists(nameBackup) || !File.Exists(nameBackup)) { /* code */ }


bool exists = File.Exists(nameBackup);

if (exists || !exists) { /* code */ }


{ /* code */ }

Verstehst Du? 😉

Also Du verstehst richtig - es ist sinnlos.
Vermutlich sah der Code mal anders aus und wurde über Monate und Jahre so oft geändert und die Entwickler haben so oft nicht weiter gedacht, bis das dabei heraus gekommen ist.
Und jetzt, da Du weiter denkst, kannst Du herausfinden, was da mal stand (vielleicht ist es auch ein Bug?) und ggf. aufräumen.

02.11.2022 - 23:55 Uhr

[...] NUR set?

Das habe ich tatsächlich schon mal gesehen. 😁
Aber einen sinnvollen Grund dafür kenne ich nicht, also nein, konkret diesen Fall brauchst Du eigentlich nicht.
Das gibt's vermutlich nur wegen der Vollständigkeit und weil es bei der Compiler-Architektur vermutlich einfach kein Aufwand war.

Du musst get und set aus Sicht des Objektes betrachten.
Darf man von außen diesen Wert ändern (set) oder nicht (kein set)?
Prinzipiell ist readonly/immutable/kein set aus Sicht der Wartbarkeit besser, da eine Instanz nicht von "außen" kaputt gemacht werden kann.
Aber natürlich gibt es auch Fälle, wo von "außen" etwas geändert werden können muss - diese Entscheidung muss man dann bei der Arbeit treffen.

02.11.2022 - 13:10 Uhr

Warum?
Weil Powershell? 😁

02.11.2022 - 02:58 Uhr

Das klingt super 🙂
Dann habe ich auch direkt noch einen Vorschlag:

Eine Art Region für den Beitragstext.
Man fässt den Text, der alles (Text, formatierter Code, etc.) sein kann, in einen Block und gibt ihm einen Namen.
Dieser Block wird dann standardmäßig eingeklappt und nur mit dem Namen sichtbar dargestellt.
Ein leser kann dann drauf klicken und ihn auf bzw. zu klappen.

Oder ihr macht (zusätzlich) alle Code-Blöcke einklappbar und bietet die Möglichkeit, ihnen einen Namen zu geben, wie es z.B. auch MS Teams macht.

Z.B. wenn in einem Beitrag viele Code-Schnipsel oder Fehlermeldungen zitiert werden, könnte man auf diese Weise die Übersicht verbessern.

02.11.2022 - 02:53 Uhr

Ich hab mich mal mit Powershell ausgetobt und ein Skript gebaut, was mir das (und mehr) automatisiert.

Einfach das Skript mit URL und (optional) Ziel-Verzeichnis aufrufen und er sucht nach einer Datei, aus der er einen Token (GitHub) liest, schreibt den Token in die URL, klont das ganze Repository und ergänzt anschließend noch eine git-config im Repository. Beide Dateien werden "nach oben" gesucht, sodass man einfach im Parent-Ordner eine Datei anlegt.
Dazu dann noch ein Haufen Parameter, weil ich hardcoded Zeug nicht mag ^^

Mir gefällt nicht, dass ich dafür einen GitHub-Token brauche und der im Klartext und ungeschützt in der config steht, aber aktuell habe ich keinen anderen Weg, wie ich mir mehreren GitHub-Accounts umgehen soll, Visual Studio kommt damit nicht klar und der Cridentials Manager auch nicht, daher der Token.
Und SSH kann's auch nicht, mal schauen, ob ich da noch mehr mache, bisher hab ich nur HTTP genutzt.

Meinung?


<#
.DESCRIPTION
    Clone a repository from a given http url into a new directory.
    If a token file is found, its content is taken as UserInfo in the URL.
    If a config file is found, it is included for the cloned repository.
.PARAMETER url
    The HTTP-URL to the repository.
.PARAMETER target
    The target folder where the repository should be placed.
.PARAMETER ignoreToken
    Do *not* add a GitHub-Token to the url.
.PARAMETER ignoreConfig
    Do *not* include a config file in the new repository.
.PARAMETER absoluteConfig
    The config file will be included with an absolute path.
.PARAMETER token
    The token to be used.
    If this parameter has a value, no token file will be searched.
.PARAMETER tokenFile
    Searches for a file with the specified name in the directory structure above the current location and uses the contents as url user info.
.PARAMETER configFile
    Searches for a file with the specified name in the directory structure above the current location and includes the file in the cloned git config.
#>

Param(
    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string]$url,
    
    [Parameter()]
    [string]$target = $null,
    
    [Parameter()]
    [switch]$ignoreToken = $false,
    
    [Parameter()]
    [switch]$ignoreConfig = $false,
    
    [Parameter()]
    [switch]$absoluteConfig = $false,
    
    [Parameter()]
    [string]$token = $null,
    
    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [string]$tokenFile = ".gittoken",
    
    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [string]$configFile = ".gitconfig"
)

$ErrorActionPreference = "Stop" 

$script = {

    if (-not $target) {
        $target = [IO.Path]::GetFileNameWithoutExtension($url)
    }

    if (-not $ignoreToken) {

        if (-not $token) {
            $token = Read-File-From-Above -file $tokenFile
        }
    
        if ($token) {
            Write-Output "Prepare url with token"
            $url = Url-With-UserInfo -url $url -userInfo $token
        } else {
            Write-Output "Warning: Could not find token file: $tokenFile"
        }
    }

    git clone $url $target

    if (-not $ignoreConfig) {

        $configFilePath = Get-Path-Of-File-Above -file $configFile
        
        if ($configFilePath) {
            $gitFolder = $target + "/.git"

            if ($absoluteConfig) {
                $configFilePath = Resolve-Path $configFile
            } else {
                $configFilePath = Get-Relative-Path -path $configFilePath -relativeTo $gitFolder
            }
            
            Write-Output "Include config file"
            git config -f $gitFolder/config include.path $configFilePath
        } else {
            Write-Output "Warning: Could not find config file: $configFile"
        }
    }
}

function Get-Relative-Path {
    param (
        [string]$path,
        [string]$relativeTo = $null
    )
    
    if ($relativeTo) {
        $previousLocation = Get-Location
        Set-Location $relativeTo
    }

    try  {
        $path = Resolve-Path -Relative $path 

    } finally {
        if ($previousLocation) {
            Set-Location $previousLocation
        }
    }

    return $path
}

function Read-File-From-Above {
    
    param (
        [string]$file
    )
    
    $file = Get-Path-Of-File-Above -file $file

    if ($file) {
        return Get-Content -Path $file
    }

    return $null
}

function Get-Path-Of-File-Above {

    param (
        [string]$file,
        [string]$folder = $null
    )

    if (-not $folder) {
        $folder = Get-Location
    }

    $fileName = Split-Path $file -Leaf
    $file = Join-Path $folder $fileName

    if (Test-Path $file -PathType Leaf) {
        return Resolve-Path $file
    }

    $folder = Split-Path $folder

    if (-not $folder) {
        return $null
    }

    return Get-Path-Of-File-Above -folder $folder -file $fileName
}

function Url-With-UserInfo {

    param (
        [string]$url,
        [string]$userInfo
    )

    $uri = New-Object -TypeName System.Uri -ArgumentList $url, [System.UriKind]::Absolute

    return "$($uri.Scheme)://$($userInfo)@$($uri.Host):$($uri.Port)$($uri.PathAndQuery)"
}

& $script

01.11.2022 - 15:41 Uhr

Naja, nicht die Antwort, die ich lesen wollte, aber irgendwie hab ich damit gerechnet.
Das beseitigt auch das Problem, dass Visual Studio immer die globale .gitconfig überschreibt 😁

31.10.2022 - 00:42 Uhr

Moin moin,

Ich habe nach einer Windows-Neuinstallation ein Problem mit meinem git-Setup - das includeif matcht nie 😠
Git habe ich mit Hilfe des Visual Studio Installers installiert, Version: 2.38.1.windows.1

Ich habe auf einem anderen Laufwerk einen Source-Ordner, in dem es wiederum mehrere Unterordner gibt und jeder dieser andere Unterordner enthält Projekte, die eine eigene config (z.B. email & name) brauchen.
Aus dem Grund habe ich mit Hilfe der globalen .gitconfig im Profil eingerichtet, dass die .gitconfig-Dateien aus jedem Unterordner abhängig vom Verzeichnis hinzugefügt werden.

Folgendes setup:

> C:\Users&lt;user>.gitconfig


# Alle vier Varianten habe ich getestet

[includeIf "gitdir/i:N:/Source/"]
    path = N:/Source/.gitconfig
	
[includeIf "gitdir:N:/Source/"]
    path = N:/Source/.gitconfig
	
[includeIf "gitdir/i:N:/Source/**"]
    path = N:/Source/.gitconfig
	
[includeIf "gitdir:N:/Source/**"]
    path = N:/Source/.gitconfig

> N:/Source/.gitconfig


[includeIf "gitdir/i:./Foo/"]
    path = ./Foo/.gitconfig

> N:/Source/Foo/.gitconfig


foo=bar

Wenn ich nun aus dem Pfad "N:\Source\Foo\Bar" heraus "git config -l --show-origin" ausführe, ist das Ergebnis:

file:C:/Users/<user>/.gitconfig includeif.gitdir/i:N:/Source/.path=N:/Source/.gitconfig
file:C:/Users/<user>/.gitconfig includeif.gitdir:N:/Source/.path=N:/Source/.gitconfig
file:C:/Users/<user>/.gitconfig includeif.gitdir/i:N:/Source/.path=N:/Source/.gitconfig
file:C:/Users/<user>/.gitconfig includeif.gitdir:N:/Source/
.path=N:/Source/.gitconfig

Auffällig ist, dass die "N:/Source/.gitconfig" scheinbar gar nicht eingelesen wird.
Wenn ich beiden Dateien das "includeif" durch ein simples "include" austausche:

file:C:/Users/<user>/.gitconfig include.path=N:/Source/.gitconfig
file:N:/Source/.gitconfig include.path=./Foo/.gitconfig
file:N:/Source/./Foo/.gitconfig foo=bar

Siehe da, es funktioniert.
Vor der Windows-Neuinstallation hat's auch funktioniert, aber der einzigen Unterschied, den ich finden kann, ist, dass ich vorher die git-Version 2.37.1 installiert hatte.
Mit Version 2.37.1 (hatte sie noch aus einer Sicherung und konnte sie kopieren) funktioniert es aber auch nicht, ich hab also irgendwas anderes falsch gemacht.

Hat jemand eine Idee, was das sein könnte oder kennt einen Weg, wie ich genaueres herausfinden kann?

Beste Grüße