通用"标识符" C#

时间:2017-02-16 15:30:03

标签: c# generics type-alias

在最终学习C#的过程中。但是在使用C ++和python之后,这是在编写C#时一直引起我注意的一件事。

C#与C ++中的typedef没有类似之处。 (或者至少htat&s;根据各种帖子,其他谷歌搜索结果。

现在第一次使用"输入别名"我可以理解(虽然从经验不同意 - 但这是我可以学会接受的东西)。

然而,我已经习惯了不同的用法,特别是在使用python多年之后: " Generic"图案。我实际上并不关心这种类型(并且我只关心它可以相互比较)。当然,通用类可以"做"这一点,但通常是矫枉过正的:特别是因为课程通常有很多课程,而且对于使用课程的人来说,它们并不重要。

一个例子,比如说我有一本字典,它会绑定"值"某些"标识符":

System.Collections.Generics.Dictionary<string, double>

将是一个合乎逻辑的开始。不过在将来说,如果对整个应用程序有更清晰的了解,我希望对其进行更改。我注意到,对于计算,我实际上需要十进制值而不是浮点数 - (或者甚至可能是bignums而不是浮点数)。我必须更改我的整个代码。 与标识符类似:字符串是&#34; easy&#34;但也许将来我真的不想使用这种臃肿的结构。相反,我使用的东西是#34;可以从字符串转换而且足够独特&#34;在我班上 或者,嘿,在不同的未来,我可能希望不使用通用字典:而是我为这个特定的类实现一个自定义字典。

所有这些都需要我在许多不同的地方更改代码。由于维护问题,潜在的错误很大,因此维护人员会选择不更改它。

在其他语言中,我了解到这一点是通过“不要照顾&#34; (python) - 或者通过允许typedef。并且总是在其他类中使用该typedef。

在C#中执行此操作的惯用方法是什么?是否普遍接受使用长&#34;列表&#34;你的类定义中的泛型变量?

MyClass<A_KeyType, A_ValueType, B_KeyType, B_ValueType, ContainerType>

由于不是用户,似乎很尴尬,但是类的维护者可能经常知道哪个更好用?

<小时/> 作为一个非常简单(愚蠢)的例子

public class MyClass {
    public MyClass() { }
    private Systems.Collections.Generics.Dictionary<string, double> Points = new Systems.Collections.Generics.Dictionary<string, double>()
    Public void AddPerson(string studentID, double val) {
        Points.Add(studentID, val)
    }

}
getter,也许是转换器等都必须明确引用Systems.Collections.Generics.Dictionary<string, double>,尽管将来可能是一个简单的数字值或其他东西。代码&#34;使用&#34;这个,#34;得到&#34;学生证需要理解它是一个字符串。

在C ++中,我会参与学生类型&#34;&#34;我的班级:

public class MyClass {
    typedef string StudentIDType
    ...

然后我会在所有情况下使用该显式类型MyClass.StudentIDType

4 个答案:

答案 0 :(得分:2)

  

C#与C ++中的typedef没有类似之处。

C中的Typedef定义了一种新类型。 C#有类型别名:

using Frob = System.Collections.Dictionary<System.String, System.String>;
...
Frob f = new Frob();

但这些每个文件。命名别名不是任何命名空间或类型的成员。

当然,C#允许您通过包装旧类型来定义新类型:

struct MyOpaqueIdentifier
{
  private int id;
  public MyOpaqueIdentifier(int id) { this.id = id; }
  ... now define equality, conversions, etc ...
}
  

但是在将来说,当我对整个应用程序有更清晰的了解时,我希望对其进行更改

这是过早的普遍性错误,也称为YAGNI:你不需要它。有无限的方法可以将程序设计得更加通用,其中大部分都是您永远不需要的。正确的做法是尽早思考你需要什么样的一般性,并从一开始就设计它们。

另外,请记住Visual Studio具有强大的重构工具。

  

在C#中执行此操作的惯用方法是什么?是否普遍接受在类定义中使用通用变量的长“列表”?

C#让您以多种方式表达一般性:

  • 基类 - 我可以接受从这个类派生的任何东西。
  • interfaces - 我可以接受任何实现此接口的内容。
  • 泛型 - 类型由n个其他类型参数化
  • 一般协方差和逆变 - 在预期有一系列动物的情况下可以使用一系列海龟
  • 泛型约束 - 类型参数约束为基本类型,接口等。
  • 委托 - 由单个方法组成的所需功能(例如:比较两个Ts是否相等)可以作为委托传递给该函数,而不需要接口,基类型等。

听起来你正在考虑滥用仿制药;通常使用其他方法之一。

答案 1 :(得分:0)

尝试这样的事情:

class Program
{
    static void Main(string[] args)
    {
        var obj = new Class1<int, string>();
        obj.Dictionary.Add(1, "hello");
    }
}

class Class1<Tkey, Tvalue>
{
    public Dictionary<Tkey, Tvalue> Dictionary { get; set; }
}

答案 2 :(得分:0)

如果您想要Python方式,请使用Dictionary<string, object>

您将牺牲性能并可能以最小化代码更改为代价遇到许多运行时问题。

我真的没有看到任何有价值的东西。维护者仍然必须到你使用float的所有地方并替换所有变量和输入。你有点想要使用强类型编译语言。

您最好的办法是创建一个包含您功能的通用类

class MyClass<T>
{
    Dictionary<string, T> innerDict;
}

答案 3 :(得分:0)

你似乎对C#中的泛型有一个基本的误解。它们意味着可以轻松地重构您的C ++ typedef似乎为将来的维护者准备切换类型的方式。这样的用法似乎是错误的,虽然我不用C ++编写代码,但我认为它不太习惯作为“通用”而更多地用作“匿名类”定义。也就是说,您实际上是在定义一个StudentIDType类型的伪类,其唯一属性是一个字符串值,您可以通过“别名”直接访问它。在C#中有一些匿名类(参见闭包),但它们被定义为某些函数的输入。相反,处理上述情况的C#方法是将伪类适当地重新声明为显式声明的类。这样的实现看起来像这样:

public class MyClass {
    // DO NOT EVER DO THIS
    // classes should not contain public classes this is merely the smallest possible example
    public class StudentPoints {
        public string StudentId { get; set; }
        public double PointsValue { get; set; }
    }

    private IEnumerable<StudentPoints> StudentPointsList = new List<StudentPoints>();

    public void AddPerson(StudentPoints studentPoints) {
        this.StudentPointsList.Add(studentPoints);
    }
}

然而,上面有一个明显的问题,它应该说明为什么匿名类是个坏主意。首先是你已经放弃了Dictionary以获得更简单的List / IEnumerable。这意味着您无法通过键访问值而无需“搜索列表”(即您不再具有哈希表实现)。第二个是在重构时仍然必须更改类型。除非您可以隐式地从一个类型转换为另一个类型,否则您仍需要更改在代码中使用的构造函数来创建StudentPoints不可避免地,更改某些内容的类型将需要对该对象的大多数(如果不是全部)引用进行代码更改。这正是构建Visual Studio中的重构工具以帮助实现的目的。但是,有一种可以使用的模式是C#,它允许您至少“减少”转换的痛苦,这样您就不必在再次编译之前找到代码库中的每个实例。该模式看起来像这样并利用重载+ [Obsolete]参数来表示您正在远离旧类型并转移到新类型:

public class MyClass {
    private Dictionary<int, double> StudentPoints = new Dictionary<int, double>(); //was string, double

    [Obsolete] // unfixed code will call this
    public void AddPerson(string studentId, double val) {
        int studentIdInt;
        if (Int32.TryParse(studentId, out studentIdInt) {
            this.AddPerson(studentIdInt, val);
            return;
        }

        throw new ArgumentException(nameof(studentId), "string not convertable to int");
    }

    public void AddPerson(int studentId, double val) {
        this.StudentList.Add(studentId, val);
    }
}

现在编译器将警告而不是在传递string时出错。这里的问题是,您现在可能会因任何string无法转换为int而出现运行时错误,否则会出现编译时错误。此外,即使第一个“reified”类作为构造函数,也可以使用此模式(重载+过时属性),但我的观点是您不需要需要来实现类(实际上它没有用) 。相反,您应该理解是的,泛型应该尽可能具体地声明其类型是的,存在重构模式,以便您可以在更改类型后相对快速地编译代码通用但它伴随着将编译时错误转化为运行时错误的交易。希望有所帮助。