当前位置: 首页 > 编程日记 > 正文

一起谈.NET技术,asp.net控件开发基础(18)

本篇继续上篇的讨论,可能大家已经在使用asp.net2.0了,DataSource属性不再使用,而是跟数据源控件搭配使用.现在讨论的绑定技术都是基于1.1版本,先熟悉一下,本质上是一样的,这样一步步的学习.对以后绝对有帮助.因为当你使用数据源控件,只需要设置一个DataSourceID,方便的同时你是否知道数据源控件帮你做了什么事情,如果你想觉的够用了,可以不用了解,但我相信你一定会有需求。上篇最后说过了,讨论还刚刚开始,我们大致把核心的方法都写出来了.下面我们继续。

  一.控件对比

我们可以使用上篇制作的TemplatedList控件跟内置控件做一下对比异同。在2.0未到来的时候,我们只有Repeater,DataList,DataGrid,现在我们也根据这三个控件进行讨论,下面把TemplatedList与DataList进行对比

(1)布局样式没DataList多...

(2)模板没DataList多...

(3)TemplatedList没ItemCollection

(4)TemplatedList没有预定义Command事件(如EditCommand,UpdateCommand等)

或者还有更多的,上面的都是次要的,布局上面我们可以改善,我们也可以添加ItemCollection,也可以预定义Command事件,但发现TemplatedList跟内置的绑定控件有几个跟数据操作严重的不同点

  (1)DataSource属性类型不同  IEnumerable和Object

为什么要将其类型设置为Object呢?

IEnumerable支持Array,ArrayList等返回类型,但却不支持DataSet类型,这是一个很严重的问题,设置其类型为Object,可以让控件支持更广泛的数据源(当然也要根据需求)这个是本次讨论的重点

  (2)DataMember 

  其用于指定数据源的特定表,由于DataSet的介入,其可能含有多个表,所以也就有了这个属性,否则的话就不需要他

  (3)DataKeyField键字段

  由于预定义Command事件的介入,实现对数据的操作,DataKeyField用于帮助数据特定记录的操作

  二.确定目标

  根据上面的对比,我们已经知道接下来要做什么了,要让控件DataSouce属性支持更多的数据源(只要还是DataSet)

  本次的demo我们将要模仿Repeater来制作,为什么不用TemplatedList?因为这样我们可以对更多控件的实现更加的熟悉,这样在使用内置控件的时候,你将明白的更透彻.此处的demo来自Building ASP.NET Server Controls书中的例子

  Repeater与TemplatedList的异同

  不同点

  大家都知道Repeater可以灵活的进行布局,所以去掉了模板样式属性,我们为其添加了多个模板属性,Repeater控件没有预定义Command事件,所以不需要DataKeyField属性.还为Repeater定义了TemplatedListmy没有的ItemCollection集合,当然也可以为TemplatedList添加这个集合最大的不同。Repeater支持DataSet,TemplatedList不支持。

  相同点

  都是数据绑定控件,所以里面很多的实现方法几乎相同,如果你看过TemplatedList的实现,再看Repeater的代码,基本没有难度,Repeater的实现比TemplatedList还要简单。

  好了,下面我们开始吧.

  三.实现

  1.为数据控件做好准备

  几乎跟上篇一样,所以不再介绍

  2.编写Repeater

  (1)定义成员属性和事件

private object dataSource;
/// <summary>
/// 绑定的列表的数据源
/// </summary>
[Category("Data"), Description("绑定的列表的数据源"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
DefaultValue(
null), Bindable(true)]
public object DataSource
{
get
{
return dataSource;
}
set
{
if ((value is IEnumerable) || (value is IListSource) || (value == null))
dataSource
= value;
else
throw new Exception("错误的数据源类型");
}
}

/// <summary>
/// 当数据绑定到列表数据源时要提取的数据成员
/// </summary>
[Category("Data"), Description("当数据绑定到列表数据源时要提取的数据成员")]
public virtual string DataMember
{
get
{
object member = ViewState["DataMember"];
if (member == null)
return string.Empty;
else
return (string)member;
}
set
{
ViewState[
"DataMember"] = value;
}
}
主要的变化在于DataSource,类型更改为object其对传入的数据源进行判断,另外还加入了DataMember属性

  (2)关键实现

  1.因为Repeater模板不具有样式属性,所以去掉了PrepareControlHierarchy方法。

  2.由于不涉及到复杂的样式属性,所以不必重载视图状态管理的三个方法。

  这两点就可以让控件减少很多代码的编写

  3.CreateControlHierarchy方法和CreateItem方法

  Repeater模板的实现方法和TemplatedList稍有不同,但变化不大,应该容易理解.看下面代码

/// <summary>
/// 创建控件各种项
/// </summary>
/// <param name="itemIndex"></param>
/// <param name="itemType"></param>
/// <param name="dataBind"></param>
/// <param name="dataItem"></param>
/// <returns></returns>
private RepeaterItem CreateItem(int itemIndex, ListItemType itemType, bool dataBind, object dataItem)
{
ITemplate selectedTemplate;
//根据不同类型创建不同项
switch (itemType)
{
case ListItemType.Header:
selectedTemplate
= headerTemplate;
break;
case ListItemType.Item:
selectedTemplate
= itemTemplate;
break;
case ListItemType.AlternatingItem:
selectedTemplate
= alternatingItemTemplate;
break;
case ListItemType.Separator:
selectedTemplate
= separatorTemplate;
break;
case ListItemType.Footer:
selectedTemplate
= footerTemplate;
break;
default:
selectedTemplate
= null;
break;
}

if ((itemType == ListItemType.AlternatingItem) &&
(alternatingItemTemplate
== null))
{
selectedTemplate
= itemTemplate;
itemType
= ListItemType.Item;
}

RepeaterItem item
= new RepeaterItem(itemIndex, itemType, dataItem);

if (selectedTemplate != null)
{
selectedTemplate.InstantiateIn(item);
}

OnItemCreated(
new RepeaterItemEventArgs(item));

Controls.Add(item);

if (dataBind)
{
item.DataBind();
OnItemDataBound(
new RepeaterItemEventArgs(item));
}
return item;
}

private ArrayList items = null;
private void CreateControlHierarchy(bool useDataSource)
{
items
= new ArrayList();
IEnumerable ds
= null;

if (HeaderTemplate != null)
{
RepeaterItem header
= CreateItem(-1, ListItemType.Header, false, null);
}

int count = -1;
if (useDataSource)
{
//解析DataSource
ds = (IEnumerable)DataSourceHelper.ResolveDataSource(DataSource,
DataMember);
}
else
{
count
= (int)ViewState["ItemCount"];
if (count != -1)
{
ds
= new DummyDataSource(count);
}
}

if (ds != null)
{
int index = 0;
count
= 0;
RepeaterItem item;
ListItemType itemType
= ListItemType.Item;

foreach (object dataItem in (IEnumerable)ds)
{
if (index != 0)
{
RepeaterItem separator
= CreateItem(-1, ListItemType.Separator, false, null);
}

item
= CreateItem(index, itemType, useDataSource, dataItem);
items.Add(item);
index
++;
count
++;

if (itemType == ListItemType.Item)
itemType
= ListItemType.AlternatingItem;
else
itemType
= ListItemType.Item;
}
}

if (FooterTemplate != null)
{
RepeaterItem footer
= CreateItem(-1, ListItemType.Footer, false, null);
}

if (useDataSource)
{
ViewState[
"ItemCount"] = ((ds != null) ? count : -1);
}
}
其中最大的变化在于这里,因为还需要支持DataSet,DataSourceHelper类负责解析传入的数据源DataSouce进行解析
            if (useDataSource)
            
{
                
//解析DataSource
                ds = (IEnumerable)DataSourceHelper.ResolveDataSource(DataSource,
                   DataMember);
            }
下面我们来重点看DataSourceHelper类,DataSourceHelper类可谓是这一篇的重头戏,关键就在于这里的理解.这里搞明白了,才算是明白.一起来看吧
// <summary>
/// 一个解析DataSource的辅助类
/// </summary>
public class DataSourceHelper
{
public static object ResolveDataSource(object dataSource, string dataMember)
{
如果数据源为空,则返回空值

如果数据源不为空,且为IEnumerable类型,则返回IEnumerable

如果数据源不为空,且为IListSource类型,则返回IListSource
return null;

}
}

这个辅助类判断太多,刚看会看晕掉的,所以在if判断这里把代码折叠起来,有助于理解。这里有几个类可能没见过,我们把关键用到的类一一列出来,希望大家查查MSDN

1.IListSource  向对象提供返回可以绑定到数据源列表的功能

2.ITypedList   提供发现可绑定列表架构的功能,其中可用于绑定的属性不同于要绑定到的对象的公共属性

3.PropertyDescriptor  提供类上的属性的抽象化

4.PropertyDescriptorCollection 表示 PropertyDescriptor 对象的集合

下面开始

(1).首先如果传入的数据源类型是IEnumerable的话,很好,可以直接返回

            if (dataSource is IEnumerable)
            
{
                
return (IEnumerable)dataSource;
            }
(2).转化实现IListSource接口的类 

  虽然传入的类型非IEnumerable,如DataSet类实现了IListSource接口,其目的就是使用此接口的GetList方法返回一个IList(IList继承IEnumerable,可以进行数据绑定),大家可以参考MSDN的原话

                IList list = null;
                IListSource listSource 
= (IListSource)dataSource;
                list 
= listSource.GetList();
假设传入的是DataSet,list将会得到System.Data.DataViewManager集合 ,DataViewManager是什么呢?为默认DataTable默认的DataViewSettingCollection

  DataViewSettingCollection是什么呢?表示DataTable的DataViewSetting的集合

  DataViewSetting是什么呢?表示从 DataViewManager 创建的 DataView 的 的默认设置

  上面的我们不熟,DataView大家应该熟悉,其可以对数据进行排序,过滤等。DataViewManager为一个默认的DataView设置集合,不知这样是否可以理解的好些。我们的目的则是将其转化到IEnumerable类型,继续DataViewManager实现了ITypedList接口。我们需要将DataViewManager(即list)转化到ITypedList ,为什么?ITypedList的GetItemProperties方法将帮助你获取DataView数据绑定的数据对象,而非DataView本身属性。

  ITypedList的GetItemProperties方法绑定数据的每项属性的PropertyDescriptorCollection集合,PropertyDescriptorCollection表示PropertyDescriptor集合,PropertyDescriptor这个类很好玩,等同于属性的说明书,即用了.net的反射技术,大家可以尝试一下,其实以前也用过这个类.下面来看代码片段

//提供发现可绑定列表架构的功能,其中可用于绑定的属性不同于要绑定到的对象的公共属性
  ITypedList typedList = (ITypedList)list;
  
//返回表示用于绑定数据的每项上属性集合
    PropertyDescriptor[] pd = new PropertyDescriptor[0];
    PropertyDescriptorCollection propDescCol 
=
    typedList.GetItemProperties(pd);  
//was (null)
   
//如果属性说明符数目为0
    if (propDescCol.Count == 0)
   
throw new Exception("ListSource without DataMembers");
  GetItemProperties方法传入了一个PropertyDescriptor的数组,大家可能注意到了传入的数组为一个空数组,你还可以传入一个空引用
PropertyDescriptorCollection propDescCol =
                       typedList.GetItemProperties(
null);  //was (null)
如果你为DataTable创建了DataView,将调用空引用返回DataSet中的一个DataTable,其将返回一个表集合列的属性描述符,继续看下去,该到DataMember出场的时候了,DataMember可以选择数据集中的特定表,如何不设置DataMember,将获取默认表,看下面代码片段
#region 判断dataMember字符数给propDesc赋值
//获取属性描述符
//若不指定dataMember属性则获取默认数据成员
if ((dataMember == null) || (dataMember.Length < 1))
{
propDesc
= propDescCol[0];
}
else
//尝试在属性集合中寻找数据成员
propDesc = propDescCol.Find(dataMember, true);

#endregion

if (propDesc == null)
throw new Exception("ListSource missing DataMember");

这样我们就得到了一个DataTablePropertyDescriptor属性描述符,继续

                    object listitem = list[0];

                    
//获取组件属性当前值
                    object member = propDesc.GetValue(listitem);

                    
if ((member == null|| !(member is IEnumerable))
                        
throw new Exception("ListSource missing DataMember");

                    
return (IEnumerable)member;

此处实现原理:

DataViewManager会在其DataSet中的DataTableCollection中搜索datamember的值进行匹配,看下图,做这么多事情,我们一直在转换

  注:GetValue用法

        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(Button1);
        PropertyDescriptor pd 
= properties.Find("Text"false);
        Button b
=new Button();
        b.Text 
= "cc";
        
object c=pd.GetValue(b);
        Response.Write(c);
        
//return cc

用GetValue方法获取listitem属性值,此属性跟datamember匹配,最后member得到的是一个DataView,DataView实现了IEnumerable,现在终于可以转换了。到此为止就结束了,现在你可以成功的传入DataSet了。

上一篇:asp.net控件开发基础(17)

下一篇:asp.net控件开发基础(19)

转载于:https://www.cnblogs.com/waw/archive/2011/09/01/2162811.html

相关文章:

使用sqlserver来存放和取得session

asp.net 提供了三种存放 session的方式。 1 InProc 2 State Server 3 SQL Server 第一种是我们经常用的&#xff0c;第2中就是使用一个名为 state server 的机器用它的内存来存放其他机器的session 状态&#xff0c;其实&#xff0c;我们还可以在 sql server 里面来存放和取…

五项挑战获四项第一,地平线霸榜Waymo自动驾驶算法挑战赛

美国当地时间6月15日&#xff0c;Alphabet&#xff08;Google母公司&#xff09;旗下的自动驾驶公司Waymo在CVPR 2020自动驾驶Workshop上揭晓Waymo开放数据集挑战赛的结果&#xff0c;边缘AI芯片企业地平线斩获5项挑战中的4项全球第一。 本次挑战赛&#xff0c;Waymo开放了其自…

SSO单点登录基于CAS架构封装 Memcached 实例

2019独角兽企业重金招聘Python工程师标准>>> SSO认证中心是CAS整个应用架构的一个极其重要的关键点&#xff0c;必须满足如下两点要求&#xff1a; 1.高可用&#xff0c;不允许程序发生故障。如果认证中心发生故障&#xff0c;整个应用群将无法登录&#xff0c;导致…

HTMLButton控件下的Confirm()

作者&#xff1a;未知 请作者速与本人联系一、前言在ASP.NET中大部分如删除等一些动作为了友好都为添加confirm()来弹出消息框进行提示&#xff0c;但是HTML控件和WEB控件是否使用的方法是一样的呢?二、方法A. System.Web.UI.WebControls.Button控件现在一般都是这样在Page_…

Python 还能实现哪些 AI 游戏?附上代码一起来一把!

作者 | 李秋键责编 | Carol头图 | CSDN 付费下载自视觉中国人工智能作为当前热门在我们生活中得到了广泛应用&#xff0c;尤其是在智能游戏方面&#xff0c;有的已经达到了可以和职业选手匹敌的效果。而DQN算法作为智能游戏的经典选择算法&#xff0c;其主要是通过奖励惩罚机制…

一起谈.NET技术,专访微软MVP衣明志:走进ASP.NET MVC 2框架开发

日前微软已经发布ASP.NET MVC 2框架RC版&#xff0c;究竟这次RC版本的发布对于WEB开发者带来怎样的改变&#xff1f;以及未来ASP.NET MVC 2正式版还会有哪些改进&#xff1f;带着这样的问题&#xff0c;我们51CTO记者彭凡专门采访了微软MVP衣明志老师。ASP.NET MVC是微软官方提…

Entity Framework:Code-First Tutorial开篇

这个系列文章是关于Entity Framework Code-First的英文系列文章&#xff0c;内容不错&#xff0c;每篇一个主题知识点介绍&#xff0c;特转载过来 原文地址&#xff1a;http://www.entityframeworktutorial.net/code-first/entity-framework-code-first.aspx转载于:https://www…

Android开发者指南(22) —— Accessing Resources

前言   本章内容为Android开发者指南的Framework Topics/Application Resources/Accessing Resources章节&#xff0c;译为"资源调用"&#xff0c;版本为Android 3.2 r1&#xff0c;翻译来自&#xff1a;"CodeGuy"&#xff0c;欢迎访问他的博客&#xff…

如何快速实现HTML编辑器.NET组件

作者&#xff1a;未知 请作者速与本人联系得到“素材”首先我们需要得到一个HTML编辑器的原始代码&#xff0c;网上有不少这类的编辑器&#xff0c;如大名鼎鼎的RichTextBox&#xff0c;为了避免版权纠纷&#xff0c;以我所做得为例&#xff08;暂名&#xff1a;UltraTextBox…

罗永浩力荐,丁磊豪送的学习神器:手机查词真不如这支AI词典笔?

销量确实称得上火爆。尽管999元的直播优惠价价格并不低&#xff0c;但这支有道词典笔专业版在快手直播间还是经历了返场&#xff0c;最终20000多台一抢而空。 为这款产品站台的正是网易CEO丁磊&#xff0c;6月11日是他网上卖货的首秀&#xff0c;不过更重要的是&#xff0c;那天…

Thinking in java中关于Exception的一道面试题.

今天看到Thinking in Java中一个关于Exception的例子:最后看到有一篇总结的比较好的文章, 这里拿来记录下, 文章地址是:http://blog.csdn.net/salerzhang/article/details/46581457 感谢原作者. 1 class Annoyance extends Exception {}2 class Sneeze extends Annoyance {}3 …

使用 .NET 框架轻松开发完美的 Web 窗体控件

作者&#xff1a;David S. Platt 出自&#xff1a;微软 本文假定您熟悉 Visual Basic .NET、C# 和 HTML 下载本文的代码&#xff1a; WebC.exe (274KB) 摘要 预建的自定义控件可以简化和加快应用程序的设计&#xff0c;并使您能够维护 UI 的一致性。但是&#xff0c;预先打…

史上最强女游戏程序员

也许你听说过John Carmack 和Tim Sweeney等大牛的名字&#xff0c;而向来游戏工业都是阳盛阴衰&#xff0c;适逢国际妇女节&#xff0c;今天我为大家介绍游戏业界一位史上最强女游戏程序员&#xff1a;Corrinne Yu。 简历 以下是她在游戏业界内的简历 微软Halo团队首席引擎架构…

重磅日程公布!与百名大咖在线交流技术,2天20个AI论坛不可错过

当全球都在面向 AI 变革时&#xff0c;AI 不再是触不可及&#xff0c;它需要产业化落地&#xff0c;为社会创造价值。在这一轮技术革命、技术浪潮中&#xff0c;开发者们成为构建任何一家AI企业的核心竞争力。不过&#xff0c;不同于此前只懂开发语言、数据结构便可轻松躲过新技…

Python取出列表相应值的位置(表处理)

#需求在一个列表中&#xff0c;取出相应值的位置方法1&#xff1a;#脚本示例[rootlocalhost opt]# cat list.py #!/usr/bin/env python #_*_ coding:utf-8 _*_ name[!,#,*,Eric,wsyht,jack,jack,a,b,c,d,1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,2332,4,2,6,2] first_pos 0 for …

rhel5.5安装xwindow

rhel5.5安装xwindow 1安装xwindow yum groupinstall "X Window System" 2、安装GNOME桌面环境 yum groupinstall "GNOME Desktop Environment" 3、卸载GNOME桌面环境 yum groupremove "GNOME Desktop Environment"转载于:https://blog.51cto…

使用 ASP.NET 加密口令

作者&#xff1a;未知 请作者速与本人联系当我们在网站上建立数据库时&#xff0c;保护用户的信息安全是非常必要的。多数用户不愿意让别人知道自己的信息&#xff0c;同时网管也不想因为安全问题而丢失网站的信誉。无论对于谁&#xff0c;安全问题都是非常重要的。为了解决这…

算法鼻祖高德纳,82 岁仍在写《计算机程序设计的艺术》

作者 | 年素清编辑 | 伍杏玲出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;高德纳&#xff08;Donald Ervin Knuth&#xff09;被誉为现代计算机科学的鼻祖&#xff0c;毕生致力于编译程序、属性文法和运算法则等领域的前沿研究&#xff0c;共出版专著17部&#x…

centos查看特定程序占用端口情况

ps axu |grep 程序名&#xff0c;找到特定程序的pidnetstat -nltp |grep pid即可。转载于:https://blog.51cto.com/zhukeqiang/1811735

关于页面刷新的问题

在做.net开发时&#xff0c;经常能碰到这样的情况&#xff0c;页面很长&#xff0c;而我们一般用的都是服务器端控件&#xff0c;用服务器端控件有这样一个缺点&#xff0c;就是控件每次都要和服务器交互&#xff0c;而产生页面的刷新&#xff0c;试想一下&#xff0c;如果页面…

技术直播:程序员副业的修炼指南!(限免报名)

面试造飞机&#xff0c;上班拧螺丝&#xff0c;每天想辞职&#xff0c;但无奈副业还“大器晚成”的样子&#xff01;那可能是你还没有选对副业&#xff01;滴滴 ~福利卡&#xff01;&#xff01;&#xff01;CSDN学院邀请汤小洋老师开设技术直播课《程序员副业之路-三大终极秘籍…

Linux 双网卡绑定测试

Linux 双网卡绑定测试 先介绍一下情况&#xff0c;服务器A和服务器B都是CentOS 4.6的系统&#xff0c;现在要做HA Cluster&#xff0c;为了避免裂脑的发生&#xff0c;要提高心跳链路的可靠性&#xff0c;下图是现时的连接情况&#xff0c;服务器A的eth2、eth3分别和服务器B的e…

第六章练习题和知识面扩充

作业题&#xff1a;1. 自动获取IP地址的命令是什么&#xff1f;您知道在什么情况下&#xff0c;您的Linux才可以自动获取IP地址&#xff1f;2. 远程连接Linux服务器&#xff0c;需要Linux服务器开启sshd服务&#xff0c;那么sshd服务默认监听哪个端口&#xff1f;这个端口是否可…

一文详解面向多级多模态场景的召回引擎

作者| 阿里文娱开发专家 崇懿、阿里文娱开发专家慧善责编 | 屠敏头图 | CSDN 下载自视觉中国出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;优酷视频搜索在文本搜索系统的基础上&#xff0c;不断探索视频搜索的方案&#xff0c;在多模态输入、多级多模态索引、跨模…

对比.Net PetShop和Duwamish来探讨Ado.Net的数据库编程模式

作者:卢彦.NET PetShop和Duwamish简单介绍相信大家一定听说过有名的"宠物店大战"&#xff0c;没错&#xff0c;本文的主角之一就是获胜方.NET PetShop&#xff0c;微软号称以27倍的速度和1/4的代码量遥遥领先于基于J2EE的PetStore宠物商店。虽然SUN也曾对此抱怨过不满…

如何直接将HTML代码加载到TWebBrowser

wbRecvContent//为 webbrowser控件 procedure TFrmMain.ShowHtmlCentent(slt: TStrings); var aMemory: TMemoryStream; pbuf: PAnsiChar; begin aMemory : TMemoryStream.Create(); try aMemory.Clear; slt.SaveToStream(aMemory); aMemory.Seek(0, soBeginning); wbRecvConte…

JavaScript基础(一) 数据类型

动态类型 JavaScript 是一种弱类型或者说动态语言。这意味着你不用提前声明变量的类型&#xff0c;在程序运行过程中&#xff0c;类型会被自动确定。 数据类型 最新的 ECMAScript 标准定义了 7 种数据类型: 6 种 原始类型: BooleanNullUndefinedNumberStringSymbol (ECMAScript…

DataGrid入门经典(C#)

作者&#xff1a;未知 请作者速与本人联系这篇文章主要介绍如何在DataGrid控件中实现编辑、删除、分类以及分页操作。为了实现我们的意图&#xff0c;我们使用SqlServer2000自带的NorthWind数据库。程序分为两部分&#xff1a; 1.包含HTML代码的.ASPX文件 2.包含所有逻辑及…

(札记)Altera Stratix IV系列FPGA TRUE LVDS RX input termination 在Quartus工程中的设置方法...

Altera Stratix IV系列FPGA Row bank的TRUE LVDS_RX支持oct&#xff08;on chip termination),所以设计的时候不需要外接一个100ohm电阻。备注&#xff1a;我使用的是友晶科技&#xff08;Terasic&#xff09;的DE4。 所以当我们使用到Stratix iv系列FPGA的row bank的lvds的时候…

直播:AI时代,普通程序员该如何转人工智能(限免报名)

常常有小伙伴在后台反馈&#xff1a;想了解人工智能&#xff0c;但是该怎么学&#xff1f;自学难度大又没有效果&#xff0c;该怎么办&#xff1f;CSDN为了解决这个难题&#xff0c;联合唐宇迪老师为大家带来了一场精彩的直播【年薪百万AI工程师亲授&#xff1a;小白实战培养计…