Was ist neu in C# 7.0 (kurze Vorschau)

巴扎黑
Freigeben: 2017-04-15 09:05:55
Original
2062 Leute haben es durchsucht

„[Übersetzung] Neue Funktionen von C# 7“ gibt viel Platz für die Einführung der 9 neuen Funktionen von C# 7.0. Hier werde ich anhand von Beispielen, die auf Projekterfahrungen basieren, eine kurze Einführung in sie geben, damit jeder sie erlernen kann Lernen Sie sie innerhalb kurzer Zeit kennen.

Im Allgemeinen erleichtern diese neuen Funktionen das Schreiben von Code mit funktionalen Programmierideen in C# 7.0. C# 6.0 hat auf diesem Weg viel Arbeit geleistet, und C# 7.0 ist einen Schritt näher gekommen!

Ausdrücke überall

In C# 6.0 können Sie Lambda-Ausdrücke für Mitgliedsmethoden und schreibgeschützte Eigenschaften verwenden. Das Frustrierendste war damals, warum der Set-Accessor der Eigenschaft nicht unterstützt wurde . Nun unterstützt nicht nur die Set-Methode die Verwendung von Lambda-Ausdrücken, sondern auch die Konstruktoren, Destruktoren und Indizes können in Lambda-Ausdrücken definiert werden.

class SomeModel
{
    private string internalValue;

    public string Value
    {
        get => internalValue;
        set => internalValue = string.IsNullOrWhiteSpace(value) ? null : value;
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

outVariablen

outVariablen sind eine Syntax, die es in C# 7.0 nur gab, um Deklaration und Verwendung zusammenzuführen, um eine weitere Codezeile zu vermeiden. Der direkteste Effekt besteht darin, dass zwei Anweisungen mit einem Ausdruck abgeschlossen werden können. Hier ist eine vereinfachte Version der Klasse Key als Beispiel. Diese Klasse wurde von uns schon früh verwendet, um über HTTP Get/Post übergebene ID-Werte zu verarbeiten.

public class Key
{
    public string Value { get; }

    public Key(string key)
    {
        Value = key;
    }

    public int IntValue
    {
        get
        {
            // C# 6.0,需要提前定义 intValue,但不需要初始化
            // 虽然 C# 6.0 可以为只读属性使用 Lambda 表达式
            // 但这里无法用一个表达式表达出来
            int intValue;
            return int.TryParse(Value, out intValue) ? intValue : 0;
        }
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

Aber in C# 7 ist es einfach

// 注意 out var intValue,
// 对于可推导的类型甚至可以用 var 来申明变量
public int IntValue => int.TryParse(Value, out var intValue) ? intValue : 0;
Nach dem Login kopieren
Nach dem Login kopieren

Tupel und Destrukturierung

Freunde, die System.Tuple verwendet haben, müssen damit vertraut seinItem1, Item2 Ich bin zutiefst beleidigt über solch eine bedeutungslose Namensgebung. C# 7.0 bietet jedoch eine semantische Benennung und reduziert gleichzeitig die Erstellung von Tupeln, sodass Tuple.Create(...) nicht mehr erforderlich ist. Um die neuen Tupelfunktionen und die Destrukturierung nutzen zu können, müssen Sie außerdem das NuGet-Paket System.ValueTuple einführen.

Install-Package System.ValueTuple
Nach dem Login kopieren
Nach dem Login kopieren

Natürlich werden Tupel oft für Methoden verwendet, die mehrere Werte zurückgeben. Manche Leute verwenden auch gerne out-Parameter für Rückgaben, aber obwohl out-Variablen jetzt möglich sind, bin ich immer noch nicht für die weit verbreitete Verwendung von out-Parametern.

Die folgende Beispielmethode wird verwendet, um einen Standardzeitbereich (insgesamt 7 Tage ab heute) für den Datenabruf zurückzugeben.

// 返回类型是一个包含两个元素的元组
(DateTime Begin, DateTime End) GetDefaultDateRange()
{
    var end = DateTime.Today.AddDays(1);
    var begin = end.AddDays(-7);

    // 这里使用一对圆括号就创建了一个元组
    return (begin, end);
}
Nach dem Login kopieren
Nach dem Login kopieren

Rufen Sie diese Methode auf, um ein Tupel abzurufen, da der Rückgabewert bei der Definition den Namen jedes Datenelements angibt. Das Abrufen von Daten aus einem Tupel kann also semantisch sein und Sie können es natürlich trotzdem verwenden Item1 und Item2.

var range = GetDefaultDateRange();
var begin = range.Begin;    // 也可以 begin = range.Item1
var end = range.End;        // 也可以 end = range.Item2
Nach dem Login kopieren
Nach dem Login kopieren

Das obige Beispiel kann ohne Verwendung der Zwischenvariablen range vereinfacht werden. Dies verwendet Destrukturierung

var (begin, end) = GetDefaultDateRange();
Nach dem Login kopieren
Nach dem Login kopieren

Das hier erstellte Tupel basiert tatsächlich auf dem Rückgabewert. Es ist ein Ausdruck und kann überall Tupel erstellen. Die Logik des obigen Beispiels ist sehr einfach und kann mithilfe von Ausdrücken gelöst werden. Das folgende Beispiel demonstriert eine nicht-semantische Rückgabetypdeklaration.

// 原来的 (DateTime Begin, DateTime End) 申明也是没问题的
(DateTime, DateTime) GetDefaultDateRange()
    => (DateTime.Today.AddDays(1).AddDays(-7), DateTime.Today.AddDays(1));
Nach dem Login kopieren
Nach dem Login kopieren

Destrukturierungsmethode Deconstrct

Die Destrukturierungsmethode ermöglicht die Destrukturierung jeder Klasse (nicht nur Tupel) gemäß definierter Parameter. Und das Erstaunliche ist, dass die Destrukturierungsmethode eine Mitgliedsmethode sein oder als Erweiterungsmethode definiert werden kann.

public class Size
{
    public int Width { get; }
    public int Height { get; }
    public int Tall { get; }

    public Size(int width, int height, int tall)
    {
        this.Width = width;
        this.Height = height;
        this.Tall = tall;
    }

    // 定义成成员方法的解构
    public void Deconstruct(out int width, out int height)
    {
        width = Width;
        height = Height;
    }
}

public static class SizeExt
{
    // 定义成扩展方法的解构
    public static void Deconstruct(this Size size, out int width, out int height, out int tall)
    {
        width = size.Width;
        height = size.Height;
        tall = size.Tall;
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

Das Folgende ist der Code mit Destrukturierung

var size = new Size(1920, 1080, 10);
var (w, h) = size;
var (x, y, z) = size;
Nach dem Login kopieren
Nach dem Login kopieren

Transformieren Sie den Konstruktor von Size

Denken Sie daran, dass der zuvor erwähnte Konstruktor als Lambda definiert werden kann Ausdrucksstil? Hier ist eine Überarbeitung des Size-Konstruktors mit Tupeln und Lambda – ich bin schon betrunken!

public Size(int width, int height, int tall)
    => (Width, Height, Tall) = (width, height, tall);
Nach dem Login kopieren
Nach dem Login kopieren

Mustervergleich

Der Mustervergleich unterstützt derzeit is und switch. Es klingt wie ein sehr hoher Name, aber um es etwas bodenständiger auszudrücken, bedeutet es, den Typ zu beurteilen und einen bestimmten Referenztyp zu definieren. Wenn Sie interessiert sind, können Sie einige zusätzliche Urteile hinzufügen.

Für is bedeutet dies, eine Variable zu definieren und sie dann zu initialisieren, wenn ein Urteil gefällt wird. Der so geschriebene Code

// 假设逻辑能保证这里的 v 可能是 string 也 可能是 int
string ToString(object v) {
    if (v is int) {
        int n = (int) v;
        return n.ToString("X4");
    } else {
        return (string) n;
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

kann also vereinfacht werden zu - okay, direkt Schreiben Sie einfach es als Ausdruck in einem Schritt niederschreiben

string ToString(object v)
    => (v is int n) ? n.ToString("X4") : (string) v;
Nach dem Login kopieren
Nach dem Login kopieren

Natürlich kann man sagen, dass das vorherige auch zu einem Ausdruck vereinfacht werden kann – okay, lass uns nicht tiefer in dieses Thema eintauchen, okay? Ich demonstriere nur den Mustervergleich für is.

Der Mustervergleich in switch scheint viel nützlicher zu sein. Nehmen wir ToString als Beispiel.

static string ToString(object v)
{
    switch (v)
    {
        case int n when n > 0xffff:
            // 判断类型,匹配的情况下再对值进行一个判断
            return n.ToString("X8");
        case int n:
            // 判断类型,这里 n 肯定 <= 0xffff
            return n.ToString("X4");
        case bool b:
            return b ? "ON" : "OFF";
        case null:
            return null;
        default:
            return v.ToString();
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

Achten Sie auf when im ersten Zweig oben Einfach gute Nutzung.

ref lokale Variable und ref Rückgabewert

Dies ist bereits eine Verwendung, die C/C++ sehr nahe kommt. Obwohl die offizielle Aussage besagt, dass dadurch einige Sicherheitsprobleme gelöst werden können, bin ich persönlich bisher noch nicht auf dessen Einsatzszenarien gestoßen. Wenn das Design gut genug ist und die neuen Funktionen und die Dekonstruktion von Tupeln derzeit hinzugefügt werden, denke ich persönlich, dass die Verwendung von out und ref fast vermieden werden kann.

Da ich es nicht benutzt habe, werde ich nicht mehr sagen, ich werde es mit Klassenkameraden besprechen, die es benutzt haben!

Verbesserungen der numerischen Literal-Syntax

Hier gibt es zwei Verbesserungen: Die eine ist die Einführung der binären numerischen Literalsyntax mit dem Präfix 0b und die andere besteht darin, dass < beliebig verwendet werden kann numerische Literale. 🎜> Gruppennummern. Hierfür ist keine Mehrheit erforderlich. Nennen Sie zum Verständnis einfach zwei Beispiele _

const int MARK_THREE = 0b11;            // 0x03
const int LONG_MARK = 0b_1111_1111;     // 0xff
const double PI = 3.14_1592_6536
Nach dem Login kopieren
Nach dem Login kopieren

局部函数

经常写 JavaScript 的同学肯定会深有体会,局部函数是个好东西。当然它在 C# 中带来的最大好处是将某些代码组织在了一起。我之前在项目中大量使用了 Lambda 来代替局部函数,现在可以直接替换成局部函数了。Labmda 和局部函数虽然多数情况下能做同样的事情,但是它们仍然有一些区别

  • 对于 Lambda,编译器要干的事情比较多。总之呢,就是编译效率要低得多

  • Lambda 通过委托实现,调用过程比较复杂,局部函数可以直接调用。简单地说就是局部函数执行效率更高

  • Lambda 必须先定义再使用,局部函数可以定义在使用之后。据说这在对递归算法的支持上会有区别

比较常用的地方是 Enumerator 函数和 async 函数中,因为它们实际都不是立即执行的。

我在项目中多是用来组织代码。局部函数代替只被某一个公共 API 调用的私有函数来组织代码虽然不失为一个简化类结构的好方法,但是把公共 API 函数的函数体拉长。所以很多时候我也会使用内部类来代替某些私有函数来组织代码。这里顺便说一句,我不赞成使用 #region 组织代码。

支持更多 async 返回类型

如果和 JavaScript 中 ES2017 的 async 相比,C# 中的 Task/Task<T> 就比较像 <code>Promise 的角色。不用羡慕 JavaScript 的 async 支持 Promise like,现在 C# 的 async 也支持 Task like 了,只要实现了 GetAwaiter 方法就行。

官方提供了一个 ValueTask 作为示例,可以通过 NuGet 引入:

Install-Package System.Threading.Tasks.Extensions
Nach dem Login kopieren
Nach dem Login kopieren

这个 ValueTask 比较有用的一点就是兼容了数据类型和 Task:

string cache;

ValueTask<string> GetData()
{
    return cache == null ? new ValueTask<string>(cache) : new ValueTask<string>(GetRemoteData());

    // 局部函数
    async Task<string> GetRemoteData()
    {
        await Task.Delay(100);
        return "hello async";
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

                                               


„[Übersetzung] Neue Funktionen von C# 7“ gibt viel Platz für die Einführung der 9 neuen Funktionen von C# 7.0. Hier werde ich anhand von Beispielen, die auf Projekterfahrungen basieren, eine kurze Einführung in sie geben, damit jeder sie erlernen kann Lernen Sie sie innerhalb kurzer Zeit kennen.

Im Allgemeinen erleichtern diese neuen Funktionen das Schreiben von Code mit funktionalen Programmierideen in C# 7.0. C# 6.0 hat auf diesem Weg viel Arbeit geleistet, und C# 7.0 ist einen Schritt näher gekommen!

Ausdrücke überall

In C# 6.0 können Sie Lambda-Ausdrücke für Mitgliedsmethoden und schreibgeschützte Eigenschaften verwenden. Das Frustrierendste war damals, warum der Set-Accessor der Eigenschaft nicht unterstützt wurde . Nun unterstützt nicht nur die Set-Methode die Verwendung von Lambda-Ausdrücken, sondern auch die Konstruktoren, Destruktoren und Indizes können in Lambda-Ausdrücken definiert werden.

class SomeModel
{
    private string internalValue;

    public string Value
    {
        get => internalValue;
        set => internalValue = string.IsNullOrWhiteSpace(value) ? null : value;
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

outVariablen

outVariablen sind eine Syntax, die es in C# 7.0 nur gab, um Deklaration und Verwendung zusammenzuführen, um eine weitere Codezeile zu vermeiden. Der direkteste Effekt besteht darin, dass zwei Anweisungen mit einem Ausdruck abgeschlossen werden können. Hier ist eine vereinfachte Version der Klasse Key als Beispiel. Diese Klasse wurde von uns schon früh verwendet, um über HTTP Get/Post übergebene ID-Werte zu verarbeiten.

public class Key
{
    public string Value { get; }

    public Key(string key)
    {
        Value = key;
    }

    public int IntValue
    {
        get
        {
            // C# 6.0,需要提前定义 intValue,但不需要初始化
            // 虽然 C# 6.0 可以为只读属性使用 Lambda 表达式
            // 但这里无法用一个表达式表达出来
            int intValue;
            return int.TryParse(Value, out intValue) ? intValue : 0;
        }
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

Aber in C# 7 ist es einfach

// 注意 out var intValue,
// 对于可推导的类型甚至可以用 var 来申明变量
public int IntValue => int.TryParse(Value, out var intValue) ? intValue : 0;
Nach dem Login kopieren
Nach dem Login kopieren

Tupel und Destrukturierung

Freunde, die System.Tuple verwendet haben, müssen damit vertraut seinItem1, Item2 Ich bin zutiefst beleidigt über solch eine bedeutungslose Namensgebung. C# 7.0 bietet jedoch eine semantische Benennung und reduziert gleichzeitig die Erstellung von Tupeln, sodass Tuple.Create(...) nicht mehr erforderlich ist. Um die neuen Tupelfunktionen und die Destrukturierung nutzen zu können, müssen Sie außerdem das NuGet-Paket System.ValueTuple einführen.

Install-Package System.ValueTuple
Nach dem Login kopieren
Nach dem Login kopieren

Natürlich werden Tupel oft für Methoden verwendet, die mehrere Werte zurückgeben. Manche Leute verwenden auch gerne out-Parameter für Rückgaben, aber obwohl out-Variablen jetzt möglich sind, bin ich immer noch nicht für die weit verbreitete Verwendung von out-Parametern.

Die folgende Beispielmethode wird verwendet, um einen Standardzeitbereich (insgesamt 7 Tage ab heute) für den Datenabruf zurückzugeben.

// 返回类型是一个包含两个元素的元组
(DateTime Begin, DateTime End) GetDefaultDateRange()
{
    var end = DateTime.Today.AddDays(1);
    var begin = end.AddDays(-7);

    // 这里使用一对圆括号就创建了一个元组
    return (begin, end);
}
Nach dem Login kopieren
Nach dem Login kopieren

Rufen Sie diese Methode auf, um ein Tupel abzurufen, da der Rückgabewert bei der Definition den Namen jedes Datenelements angibt. Das Abrufen von Daten aus einem Tupel kann also semantisch sein und Sie können es natürlich trotzdem verwenden Item1 und Item2.

var range = GetDefaultDateRange();
var begin = range.Begin;    // 也可以 begin = range.Item1
var end = range.End;        // 也可以 end = range.Item2
Nach dem Login kopieren
Nach dem Login kopieren

Das obige Beispiel kann ohne Verwendung der Zwischenvariablen range vereinfacht werden. Dies verwendet Destrukturierung

var (begin, end) = GetDefaultDateRange();
Nach dem Login kopieren
Nach dem Login kopieren

Das hier erstellte Tupel basiert tatsächlich auf dem Rückgabewert. Es ist ein Ausdruck und kann überall Tupel erstellen. Die Logik des obigen Beispiels ist sehr einfach und kann mithilfe von Ausdrücken gelöst werden. Das folgende Beispiel demonstriert eine nicht-semantische Rückgabetypdeklaration.

// 原来的 (DateTime Begin, DateTime End) 申明也是没问题的
(DateTime, DateTime) GetDefaultDateRange()
    => (DateTime.Today.AddDays(1).AddDays(-7), DateTime.Today.AddDays(1));
Nach dem Login kopieren
Nach dem Login kopieren

Destrukturierungsmethode Deconstrct

Die Destrukturierungsmethode ermöglicht die Destrukturierung jeder Klasse (nicht nur Tupel) gemäß definierter Parameter. Und das Erstaunliche ist, dass die Destrukturierungsmethode eine Mitgliedsmethode sein oder als Erweiterungsmethode definiert werden kann.

public class Size
{
    public int Width { get; }
    public int Height { get; }
    public int Tall { get; }

    public Size(int width, int height, int tall)
    {
        this.Width = width;
        this.Height = height;
        this.Tall = tall;
    }

    // 定义成成员方法的解构
    public void Deconstruct(out int width, out int height)
    {
        width = Width;
        height = Height;
    }
}

public static class SizeExt
{
    // 定义成扩展方法的解构
    public static void Deconstruct(this Size size, out int width, out int height, out int tall)
    {
        width = size.Width;
        height = size.Height;
        tall = size.Tall;
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

Das Folgende ist der Code mit Destrukturierung

var size = new Size(1920, 1080, 10);
var (w, h) = size;
var (x, y, z) = size;
Nach dem Login kopieren
Nach dem Login kopieren

Transformieren Sie den Konstruktor von Size

Denken Sie daran, dass der zuvor erwähnte Konstruktor als Lambda definiert werden kann Ausdrucksstil? Hier ist eine Überarbeitung des Size-Konstruktors mit Tupeln und Lambda – ich bin schon betrunken!

public Size(int width, int height, int tall)
    => (Width, Height, Tall) = (width, height, tall);
Nach dem Login kopieren
Nach dem Login kopieren

Mustervergleich

Der Mustervergleich unterstützt derzeit is und switch. Es klingt wie ein sehr hoher Name, aber um es etwas bodenständiger auszudrücken, bedeutet es, den Typ zu beurteilen und einen bestimmten Referenztyp zu definieren. Wenn Sie interessiert sind, können Sie einige zusätzliche Urteile hinzufügen.

Für is bedeutet dies, eine Variable zu definieren und sie dann zu initialisieren, wenn ein Urteil gefällt wird. Der so geschriebene Code

// 假设逻辑能保证这里的 v 可能是 string 也 可能是 int
string ToString(object v) {
    if (v is int) {
        int n = (int) v;
        return n.ToString("X4");
    } else {
        return (string) n;
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

kann also vereinfacht werden zu - okay, direkt Schreiben Sie einfach es als Ausdruck in einem Schritt niederschreiben

string ToString(object v)
    => (v is int n) ? n.ToString("X4") : (string) v;
Nach dem Login kopieren
Nach dem Login kopieren

Natürlich kann man sagen, dass das vorherige auch zu einem Ausdruck vereinfacht werden kann – okay, lass uns nicht tiefer in dieses Thema eintauchen, okay? Ich demonstriere nur den Mustervergleich für is.

Der Mustervergleich in switch scheint viel nützlicher zu sein. Nehmen wir ToString als Beispiel.

static string ToString(object v)
{
    switch (v)
    {
        case int n when n > 0xffff:
            // 判断类型,匹配的情况下再对值进行一个判断
            return n.ToString("X8");
        case int n:
            // 判断类型,这里 n 肯定 <= 0xffff
            return n.ToString("X4");
        case bool b:
            return b ? "ON" : "OFF";
        case null:
            return null;
        default:
            return v.ToString();
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

Achten Sie auf when im ersten Zweig oben Einfach gute Nutzung.

ref lokale Variable und ref Rückgabewert

Dies ist bereits eine Verwendung, die C/C++ sehr nahe kommt. Obwohl die offizielle Aussage besagt, dass dadurch einige Sicherheitsprobleme gelöst werden können, bin ich persönlich bisher noch nicht auf dessen Einsatzszenarien gestoßen. Wenn das Design gut genug ist und die neuen Funktionen und die Dekonstruktion von Tupeln derzeit hinzugefügt werden, denke ich persönlich, dass die Verwendung von out und ref fast vermieden werden kann.

Da ich es nicht benutzt habe, werde ich nicht mehr sagen, ich werde es mit Klassenkameraden besprechen, die es benutzt haben!

Verbesserungen der numerischen Literal-Syntax

Hier gibt es zwei Verbesserungen: Die eine ist die Einführung der binären numerischen Literalsyntax mit dem Präfix 0b und die andere besteht darin, dass < beliebig verwendet werden kann numerische Literale. 🎜> Gruppennummern. Hierfür ist keine Mehrheit erforderlich. Nennen Sie zum Verständnis einfach zwei Beispiele _

const int MARK_THREE = 0b11;            // 0x03
const int LONG_MARK = 0b_1111_1111;     // 0xff
const double PI = 3.14_1592_6536
Nach dem Login kopieren
Nach dem Login kopieren

局部函数

经常写 JavaScript 的同学肯定会深有体会,局部函数是个好东西。当然它在 C# 中带来的最大好处是将某些代码组织在了一起。我之前在项目中大量使用了 Lambda 来代替局部函数,现在可以直接替换成局部函数了。Labmda 和局部函数虽然多数情况下能做同样的事情,但是它们仍然有一些区别

  • 对于 Lambda,编译器要干的事情比较多。总之呢,就是编译效率要低得多

  • Lambda 通过委托实现,调用过程比较复杂,局部函数可以直接调用。简单地说就是局部函数执行效率更高

  • Lambda 必须先定义再使用,局部函数可以定义在使用之后。据说这在对递归算法的支持上会有区别

比较常用的地方是 Enumerator 函数和 async 函数中,因为它们实际都不是立即执行的。

我在项目中多是用来组织代码。局部函数代替只被某一个公共 API 调用的私有函数来组织代码虽然不失为一个简化类结构的好方法,但是把公共 API 函数的函数体拉长。所以很多时候我也会使用内部类来代替某些私有函数来组织代码。这里顺便说一句,我不赞成使用 #region 组织代码。

支持更多 async 返回类型

如果和 JavaScript 中 ES2017 的 async 相比,C# 中的 Task/Task<T> 就比较像 <code>Promise 的角色。不用羡慕 JavaScript 的 async 支持 Promise like,现在 C# 的 async 也支持 Task like 了,只要实现了 GetAwaiter 方法就行。

官方提供了一个 ValueTask 作为示例,可以通过 NuGet 引入:

Install-Package System.Threading.Tasks.Extensions
Nach dem Login kopieren
Nach dem Login kopieren

这个 ValueTask 比较有用的一点就是兼容了数据类型和 Task:

string cache;

ValueTask<string> GetData()
{
    return cache == null ? new ValueTask<string>(cache) : new ValueTask<string>(GetRemoteData());

    // 局部函数
    async Task<string> GetRemoteData()
    {
        await Task.Delay(100);
        return "hello async";
    }
}
Nach dem Login kopieren
Nach dem Login kopieren



Das obige ist der detaillierte Inhalt vonWas ist neu in C# 7.0 (kurze Vorschau). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage