百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Prism+MaterialDesign+EntityFramework+Postgresql WPF之 基础篇

ccwgpt 2024-09-27 07:19 28 浏览 0 评论

本着每天记录一点成长一点的原则,打算将目前完成的一个WPF项目相关的技术分享出来,供团队学习与总结。


总共分三个部分:

  • 基础篇主要争对C#初学者,巩固C#常用知识点;
  • 中级篇主要争对WPF布局与美化,在减轻代码量的情况做出漂亮的应用;
  • 终极篇为框架应用实战,包含MVVM框架Prism,ORM框架EntityFramework Core,开源数据库Postgresql。

目录

  1. Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 基础篇
  2. Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 中级篇(待续)
  3. Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 终极篇(待续)

前言

此篇为C#常用知识点的实例说明,如果你是多年C#开发者可以跳过此篇或者只关注最后的新特性。

1、OOP之源 类与实例

一切事物皆对象。

类像产品模版,用它可以生产很多产品(简称实例对象)。

类:具有相同属性和行为的对象的抽象集合。实例对象:具备一组可识别的特性与行为的实体。

举个例子:张三、李四。

幻化出类如下:属性为名字,实例就是张三、李四。

public class Person
{
    public string Name { get; set; }
    public Person(string name)
    {
        Name = name;
    }
}

张三=new Person("张三")
李四=new Person("李四")

类与属性的修饰符中需要特别关注如下三个:


sealed:密封效果

  • 修饰类时,类将不可以作为基类继承。
  • 修饰属性与行为时,属性与行为在继承类中无法Override与New。
sealed class SealedClass
{
    public int x;
    public int y;
}

// error class SealedTest2:SealedClass
class SealedTest2
{
    static void Main()
    {
        var sc = new SealedClass();
        sc.x = 110;
        sc.y = 150;
        Console.WriteLine(#34;x = {sc.x}, y = {sc.y}");
    }
}
// Output: x = 110, y = 150

//------------------
class X
{
    protected virtual void F() { Console.WriteLine("X.F"); }
    protected virtual void F2() { Console.WriteLine("X.F2"); }
}

class Y : X
{
    sealed protected override void F() { Console.WriteLine("Y.F"); }
    protected override void F2() { Console.WriteLine("Y.F2"); }
}

class Z : Y
{
    // Attempting to override F causes compiler error CS0239.
    // protected override void F() { Console.WriteLine("Z.F"); }

    // Overriding F2 is allowed.
    protected override void F2() { Console.WriteLine("Z.F2"); }
}

internal:程序集访问控制

  • 只有在同一程序集的文件中,内部类型或成员才可访问。
  • 可以修饰类与成员,通常用于组件开发。
// Assembly1.cs  
// Compile with: /target:library  
internal class BaseClass
{  
   public static int intM = 0;  
}
// Assembly1_a.cs  
// Compile with: /reference:Assembly1.dll  
class TestAccess
{  
   static void Main()
   {  
      var myBase = new BaseClass();   // CS0122  错误
   }  
}

protected:成员访问控制

  • 只能修饰成员,不可以修饰类。
  • 修饰的成员,派生类内部可以直接访问。
class A
{
    protected int x = 123;
}

class B : A
{
    static void Main()
    {
        var a = new A();
        var b = new B();

        // Error CS1540, because x can only be accessed by
        // classes derived from A.
        // a.x = 10; 

        // OK, because this class derives from A.
        b.x = 10;
    }
}

2、OOP之三大特性:

封装

实例化的每个对象都包含它能进行操作所需要的全部信息。

好处:减少耦合,在类结构变化时创建的对象可以跟随变化;设置访问限制,对外接口清晰明了。

继承 (is-a关系)

站在巨人的肩膀上才能飞得更高。

通过继承,除了被继承类的特性之外,可以通过重写、添加和修改创建自己的独有特性。由于父子关系的原因导致类之间强耦合,最好在is-a关系时使用,has-a关系就不行了。

  • New修饰:显式指示成员不应作为基类成员的重写。
  • 抽象方法:abstract 修饰,必须在直接继承自该类的任何非抽象类中重写该方法。 如果派生类本身是抽象的,则它会继承抽象成员而不会实现它们。
  • 虚方法:virtual修饰,派生类可以根据需要重写该方法,也可以直接使用基类的实现。

多态

代码使用基类方法,实际运行时调用派生类对象的重写方法。

相当于派生类的对象可以作为基类的对象处理。 在出现此多形性时,该对象的声明类型不再与运行时类型相同。

public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }
   
    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
public class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
public class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used whereever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived 
// class to its base class.
var shapes = new List<Shape>
{
    new Rectangle(),
    new Triangle(),
    new Circle()
};

// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
    shape.Draw();
}
/* Output:
    Drawing a rectangle
    Performing base class drawing tasks
    Drawing a triangle
    Performing base class drawing tasks
    Drawing a circle
    Performing base class drawing tasks
*/

3、接口与抽象类

在设计的时候有时候很难决定用那种。原则有一个:如果不想波及子类的修改就用抽象类。因为接口定义之后,继承的类必须实现此接口。

  • 接口:包含方法、 属性、 事件和索引器,成员隐式都具有公共访问权限,接口只定义不实现成员。一个接口可能从多个基接口继承,类或结构可以实现多个接口。
public delegate void StringListEvent(IStringList sender);

public interface IStringList
{
    void Add(string s);
    int Count { get; }
    event StringListEvent Changed;
    string this[int index] { get; set; }
}
  • 抽象类:包含抽象方法,虚方法,特有行为,属性,成员的访问权限可控制。sealed不可以使用,且只能继承一个抽象类。
abstract class A
{
    public abstract void F();
}

abstract class B: A
{
    public void G() {}
}

class C: B
{
    public override void F() {
        // actual implementation of F
    }
}

4、泛型

在客户端代码声明并初始化这些类或方法之前,这些类或方法会延迟指定一个或多个类型。不会产生运行时转换或装箱操作的成本或风险。

  • 使用泛型类型可以最大限度地重用代码、保护类型安全性以及提高性能。
  • 泛型最常见的用途是创建集合类。
  • 可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
  • 可以对泛型类进行约束以访问特定数据类型的方法。
// Declare the generic class.
public class GenericList<T>
{
    public void Add(T input) { }
}
class TestGenericList
{
    private class ExampleClass { }
    static void Main()
    {
        // Declare a list of type int.
        GenericList<int> list1 = new GenericList<int>();
        list1.Add(1);

        // Declare a list of type string.
        GenericList<string> list2 = new GenericList<string>();
        list2.Add("");

        // Declare a list of type ExampleClass.
        GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
        list3.Add(new ExampleClass());
    }
}

泛型约束:

class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new()
{ }

where T : notnull 指定类型参数必须是不可为 null 的值类型或不可为 null 的引用类型(C# 8.0)

where T : unmanaged 指定类型参数必须是不可为 null 的非托管类型(C# 7.3).通过 unmanaged 约束,用户能编写可重用例程,从而使用可作为内存块操作的类型。

unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged
{
    var size = sizeof(T);
    var result = new Byte[size];
    Byte* p = (byte*)&argument;
    for (var i = 0; i < size; i++)
        result[i] = *p++;
    return result;
}

5、扩展方法

扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。Dapper,System.Linq(对System.Collections.IEnumerable 和 System.Collections.Generic.IEnumerable<T> 的扩展)就是经典使用。

扩展方法的优先级总是比类型本身中定义的实例方法低,且只能访问公有成员。 只在不得已的情况下才实现扩展方法。

如果确实为给定类型实现了扩展方法,请记住以下几点:

  • 如果扩展方法与该类型中定义的方法具有相同的签名,则扩展方法永远不会被调用。
  • 在命名空间级别将扩展方法置于范围中。 例如,如果你在一个名为 Extensions 的命名空间中具有多个包含扩展方法的静态类,则这些扩展方法将全部由 using Extensions; 指令置于范围中。
namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this String str)
        {
            return str.Split(new char[] { ' ', '.', '?' }, 
                             StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }   
}

6、语言集成查询(LINQ)

一系列直接将查询功能集成到 C# 语言的技术统称。 传统上,针对数据的查询都以简单的字符串表示,而没有编译时类型检查或 IntelliSense 支持。 此外,还需要针对每种数据源学习一种不同的查询语言:SQL 数据库、XML 文档、各种 Web 服务等等。 借助 LINQ,查询成为了最高级的语言构造,就像类、方法和事件一样。

简单点就是使用查询表达式可以查询和转换 SQL 数据库、ADO .NET 数据集、XML 文档和流以及 .NET 集合中的数据。超越了限定于集合的扩展方法System.Linq。Entity Framework就是基于这个自动生成查询Sql操作数据。

  • 查询表达式中的变量全都是强类型,尽管在许多情况下,无需显式提供类型,因为编译器可以推断出。 有关详细信息,请参阅 LINQ 查询操作中的类型关系。
  • 只有在循环访问查询变量后,才会执行查询(例如,在 foreach 语句中)。 有关详细信息,请参阅 LINQ 查询简介。
  • 应用程序始终将源数据视为 IEnumerable<T> 或 IQueryable<T> 集合。

查询表达式:

查询表达式必须以 from 子句开头,且必须以 select 或 group 子句结尾。 在第一个 from 子句与最后一个 select 或 group 子句之间,可以包含以下这些可选子句中的一个或多个:where、orderby、join、let,甚至是其他 from 子句。 还可以使用 into 关键字,使 join 或 group 子句的结果可以充当相同查询表达式中的其他查询子句的源。

// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
    from country in countries
    let percentile = (int) country.Population / 10_000_000
    group country by percentile into countryGroup
    where countryGroup.Key >= 20
    orderby countryGroup.Key
    select countryGroup;

// grouping is an IGrouping<int, Country>
foreach (var grouping in percentileQuery)
{
    Console.WriteLine(grouping.Key);
    foreach (var country in grouping)
        Console.WriteLine(country.Name + ":" + country.Population);
}

参见内联实例:

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

class Cat : Pet
{ }

class Dog : Pet
{ }

public static void MultipleJoinExample()
{
    Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
    Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
    Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
    Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
    Person rui = new Person { FirstName = "Rui", LastName = "Raposo" };
    Person phyllis = new Person { FirstName = "Phyllis", LastName = "Harris" };

    Cat barley = new Cat { Name = "Barley", Owner = terry };
    Cat boots = new Cat { Name = "Boots", Owner = terry };
    Cat whiskers = new Cat { Name = "Whiskers", Owner = charlotte };
    Cat bluemoon = new Cat { Name = "Blue Moon", Owner = rui };
    Cat daisy = new Cat { Name = "Daisy", Owner = magnus };

    Dog fourwheeldrive = new Dog { Name = "Four Wheel Drive", Owner = phyllis };
    Dog duke = new Dog { Name = "Duke", Owner = magnus };
    Dog denim = new Dog { Name = "Denim", Owner = terry };
    Dog wiley = new Dog { Name = "Wiley", Owner = charlotte };
    Dog snoopy = new Dog { Name = "Snoopy", Owner = rui };
    Dog snickers = new Dog { Name = "Snickers", Owner = arlene };

    // Create three lists.
    List<Person> people =
        new List<Person> { magnus, terry, charlotte, arlene, rui, phyllis };
    List<Cat> cats =
        new List<Cat> { barley, boots, whiskers, bluemoon, daisy };
    List<Dog> dogs =
        new List<Dog> { fourwheeldrive, duke, denim, wiley, snoopy, snickers };

    // The first join matches Person and Cat.Owner from the list of people and
    // cats, based on a common Person. The second join matches dogs whose names start
    // with the same letter as the cats that have the same owner.
    var query = from person in people
                join cat in cats on person equals cat.Owner
                join dog in dogs on 
                new { Owner = person, Letter = cat.Name.Substring(0, 1) }
                equals new { dog.Owner, Letter = dog.Name.Substring(0, 1) }
                select new { CatName = cat.Name, DogName = dog.Name };

    foreach (var obj in query)
    {
        Console.WriteLine( 
            #34;The cat \"{obj.CatName}\" shares a house, and the first letter of their name, with \"{obj.DogName}\".");  
    }
}

// This code produces the following output:
//
// The cat "Daisy" shares a house, and the first letter of their name, with "Duke".
// The cat "Whiskers" shares a house, and the first letter of their name, with "Wiley".

详细参照:https://docs.microsoft.com/zh-cn/dotnet/csharp/linq/

7、lambda表达式

匿名函数演变而来。匿名函数是一个“内联”语句或表达式,可在需要委托类型的任何地方使用。 可以使用匿名函数来初始化命名委托,或传递命名委托(而不是命名委托类型)作为方法参数。

在 C# 1.0 中,通过使用在代码中其他位置定义的方法显式初始化委托来创建委托的实例。 C# 2.0 引入了匿名方法的概念,作为一种编写可在委托调用中执行的未命名内联语句块的方式。 C# 3.0 引入了 lambda 表达式,这种表达式与匿名方法的概念类似,但更具表现力并且更简练。任何 Lambda 表达式都可以转换为委托类型。 Lambda 表达式可以转换的委托类型由其参数和返回值的类型定义。 如果 lambda 表达式不返回值,则可以将其转换为 Action 委托类型之一;否则,可将其转换为 Func 委托类型之一。

class Test
{
    delegate void TestDelegate(string s);
    static void M(string s)
    {
        Console.WriteLine(s);
    }

    static void Main(string[] args)
    {
        // Original delegate syntax required 
        // initialization with a named method.
        TestDelegate testDelA = new TestDelegate(M);

        // C# 2.0: A delegate can be initialized with
        // inline code, called an "anonymous method." This
        // method takes a string as an input parameter.
        TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };

        // C# 3.0. A delegate can be initialized with
        // a lambda expression. The lambda also takes a string
        // as an input parameter (x). The type of x is inferred by the compiler.
        TestDelegate testDelC = (x) => { Console.WriteLine(x); };

        // Invoke the delegates.
        testDelA("Hello. My name is M and I write lines.");
        testDelB("That's nothing. I'm anonymous and ");
        testDelC("I'm a famous author.");

        // Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Hello. My name is M and I write lines.
    That's nothing. I'm anonymous and
    I'm a famous author.
    Press any key to exit.
 */

8、元组

使用之前必须添加 NuGet 包 System.ValueTuple,C# 7.0添加的新功能。

元组主要作为返回多个值方法的返回值,简化定义返回值类型的麻烦。与类和结构一样,使用数据结构存储多个元素,但不定义行为。 既可以获得静态类型检查的所有好处,又不需要使用更复杂的 class 或 struct 语法来创作类型。元组还是对 private 或 internal 这样的实用方法最有用。不过公共方法返回具有多个元素的值时,请创建用户定义的类型(class 或 struct 类型)。

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var computation = ComputeSumAndSumOfSquares(sequence);

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    int count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    return (count, sum, sumOfSquares);
}

元组析构:

public static void Main()
{
    (string city, int population, double area) = QueryCityData("New York City");

    // Do something with the data.
}
public static void Main()
{
    var (city, population, area) = QueryCityData("New York City");

    // Do something with the data.
}
public static void Main()
{
    (string city, var population, var area) = QueryCityData("New York City");

    // Do something with the data.
}
public static void Main()
{
    string city = "Raleigh";
    int population = 458880;
    double area = 144.8;

    (city, population, area) = QueryCityData("New York City");

    // Do something with the data.
} 

弃元析构:

using System;
using System.Collections.Generic;

public class Example
{
    public static void Main()
    {
        var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

        Console.WriteLine(#34;Population change, 1960 to 2010: {pop2 - pop1:N0}");
    }
   
    private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
    {
        int population1 = 0, population2 = 0;
        double area = 0;
      
        if (name == "New York City")
        {
            area = 468.48; 
            if (year1 == 1960)
            {
                population1 = 7781984;
            }
            if (year2 == 2010)
            {
                population2 = 8175133;
            }
            return (name, area, year1, population1, year2, population2);
        }

        return ("", 0, 0, 0, 0, 0);
    }
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149 

类型的Deconstruct用户自定义析构:

using System;

public class Person
{
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public string City { get; set; }
    public string State { get; set; }

    public Person(string fname, string mname, string lname, 
                  string cityName, string stateName)
    {
        FirstName = fname;
        MiddleName = mname;
        LastName = lname;
        City = cityName;
        State = stateName;
    }

    // Return the first and last name.
    public void Deconstruct(out string fname, out string lname)
    {
        fname = FirstName;
        lname = LastName;
    }

    public void Deconstruct(out string fname, out string mname, out string lname)
    {
        fname = FirstName;
        mname = MiddleName;
        lname = LastName;
    }

    public void Deconstruct(out string fname, out string lname, 
                            out string city, out string state)
    {
        fname = FirstName;
        lname = LastName;
        city = City;
        state = State;
    }
}

public class Example
{
    public static void Main()
    {
        var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

        // Deconstruct the person object.
        var (fName, lName, city, state) = p;
        Console.WriteLine(#34;Hello {fName} {lName} of {city}, {state}!");
    }
}
// The example displays the following output:
//    Hello John Adams of Boston, MA! 

本人博客园地址:https://www.cnblogs.com/lixiaobin/p/wpfdevreportBasic.html

相关推荐

Python+ Appium:Android手机连接与操作详解(附源码)

在移动端自动化测试领域,Appium一直是最热门的开源工具之一。今天这篇文章,我们聚焦Android端自动化测试的完整流程,从环境配置到代码实战,一步一步带你掌握用Python控制Android...

全平台开源即时通讯IM框架MobileIMSDK开发指南,支持鸿蒙NEXT

写在前面在着手基于MobileIMSDK开发自已的即时通讯应用前,建议以Demo工程为脚手架,快速上手MobileIMSDK!Demo工程主要用于演示SDK的API调用等,它位于SDK完整下载包的如下...

移动开发(一):使用.NET MAUI开发第一个安卓APP

对于工作多年的C#程序员来说,近来想尝试开发一款安卓APP,考虑了很久最终选择使用.NETMAUI这个微软官方的框架来尝试体验开发安卓APP,毕竟是使用VisualStudio开发工具,使用起来也...

在安卓系统上开发一款软件详细的流程

安卓app软件开发流程是一个系统而复杂的过程,涉及多个阶段和环节。以下是一个典型的安卓软件开发流程概述:1.需求分析目的:了解用户需求,确定APP的目标、功能、特性和预期效果。活动:开发团队与客户进...

ArkUI-X在Android上使用Fragment开发指南

本文介绍将ArkUI框架的UIAbility跨平台部署至Android平台Fragment的使用说明,实现Android原生Fragment和ArkUI跨平台Fragment的混合开发,方便开发者灵活...

Web3开发者必须要知道的6个框架与开发工具

在Web3领域,随着去中心化应用和区块链的兴起,开发者们需要掌握适用于这一新兴技术的框架与开发工具。这些工具和框架能够提供简化开发流程、增强安全性以及提供更好的用户体验。1.Truffle:Truff...

Python开发web指南之创建你的RESTful APP

上回我们说到了:PythonFlask开发web指南:创建RESTAPI。我们知道了Flask是一个web轻量级框架,可以在上面做一些扩展,我们还用Flask创建了API,也说到了...

python的web开发框架有哪些(python主流web框架)

  python在web开发方面有着广泛的应用。鉴于各种各样的框架,对于开发者来说如何选择将成为一个问题。为此,我特此对比较常见的几种框架从性能、使用感受以及应用情况进行一个粗略的分析。  1Dja...

Qwik:革新Web开发的新框架(webview开源框架)

听说关注我的人,都实现了财富自由!你还在等什么?赶紧加入我们,一起走向人生巅峰!Qwik:革新Web开发的新框架Qwik橫空出世:一场颠覆前端格局的革命?是炒作还是未来?前端框架的更新迭代速度,如同...

Python中Web开发框架有哪些?(python主流web框架)

Python为Web开发提供了许多优秀的框架。以下是一些流行的PythonWeb框架:1.Django:一个高级的Web框架,旨在快速开发干净、实用的Web应用。Django遵...

WPF 工业自动化数据管控框架,支持热拔插 DLL与多语言实现

前言工业自动化开发中,设备数据的采集、处理与管理成为提升生产效率和实现智能制造的关键环节。为了简化开发流程、提高系统的灵活性与可维护性,StarRyEdgeFramework应运而生。该框架专注...

[汇川PLC] 汇川IFA程序框架06-建立气缸控制FB块

前言:汇川的iFA要跟西门子对标啦,这可是新的选择!就在2月14日,汇川刚发布的iFA平台,一眼就能看出来是对标西门子的全集成自动化平台博途(TIAPortal)。这个平台能在同一个...

微软发布.NET 10首个预览版:JIT编译器再进化、跨平台开发更流畅

IT之家2月26日消息,微软.NET团队昨日(2月25日)发布博文,宣布推出.NET10首个预览版更新,重点改进.NETRuntime、SDK、libraries、C#、AS...

大模型部署革命:GGUF量化+vLLM推理的极致性能调优方案

本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在官网-聚客AI学院大模型应用开发微调项目实践课程学习平台一、模型微调核心概念与技术演进1.1微调的本质与优势数学表达:1....

拓扑学到底在研究什么?(拓扑学到底在研究什么问题)

拓扑是“不量尺寸的几何学”,那么它的核心内容,主要方法是什么?如果你问罗巴切夫斯基,他会说“附贴性是物体的一个特殊的属性。如果我们把这个性质掌握,而把物体其他的一切属性,不问是本质的或偶然出现的,均不...

取消回复欢迎 发表评论: